# Node.js -------------------------------------------------------------------------------- title: "Introduction" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.673Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/introduction/" service: "All Services" related: - Project Directory Structure (/en/cli/v1/project-directory-structure/introduction/) - Dynamic Cron (/en/job-scheduling/help/cron/key-concepts/#cron-types) - Job Scheudling SDK (/en/sdk/nodejs/v2/job-scheduling/overview/) - Job Scheduling API (/en/api/code-reference/job-scheduling/jobpool/get-all-jobpool/#GetAllJobPools) -------------------------------------------------------------------------------- # Birthday Greetings App ### Introduction This tutorial will help you build a simple React web application called **Birthday Greetings**. The application will allow you to configure email wishes you can send to your peers on their birthday. You can configure the wish with a custom birthday message and set it to be sent to the required person's email on the day of their birthday. You can perform the following actions in the application: * **Sign up**/**log in** to your application and employ the common authentication processes, including validating access using custom logic. * **Create**, **Update**, **Delete**, and manage the birthday wishes you configure. The client side of the application will look like this: <br /> You can explore the functionality of the application using this link: {{%link href="https://jobscheduling-tutorial-860829830.development.catalystserverless.com/app/index.html" %}}Try the app!{{%/link%}} The backend logic of this tutorial is coded using the **Node.js** runtime. The Birthday Greeting application employs features of components from the following Catalyst services: 1. {{%bold%}}{{%link href="/en/serverless/" %}}Catalyst Serverless{{%/link%}}{{%/bold%}}<br /> - {{%link href="/en/serverless/help/functions/introduction/" %}}Functions{{%/link%}}: The backend logic will be coded using the following function types: - {{%link href="/en/serverless/help/functions/advanced-io/" %}}Advanced I/O Function{{%/link%}}: The logic to interact with the client and create the dynamic cron will be coded in this function. - {{%link href="/en/serverless/help/functions/basic-io/" %}}Basic I/O Function{{%/link%}}: The logic used for custom user validation will be coded in this function. - {{%link href="/en/serverless/help/functions/job-functions/" %}}Job Function{{%/link%}}: The logic to trigger the email containing the birthday greeting will be coded in this function. 2. {{%bold%}}{{%link href="/en/cloud-scale/" %}}Catalyst Cloud Scale{{%/link%}}{{%/bold%}}<br /> - {{%link href="/en/cloud-scale/help/data-store/introduction/" %}}Data Store{{%/link%}}: To store the name, custom message, birthday, and email of the person you are creating and scheduling the greeting for. - {{%link href="/en/cloud-scale/help/zcql/introduction/" %}}ZCQL{{%/link%}}: To post and fetch data from the Data Store through querying. - {{%link href="/en/cloud-scale/help/authentication/introduction/" %}}Authentication{{%/link%}}: To employ the required login elements using the {{%link href="/en/cloud-scale/help/authentication/native-catalyst-authentication/embedded-authentication/introduction/" %}}Embedded Authentication{{%/link%}} type to allow users to sign up or log in to your application. - {{%link href="/en/cloud-scale/help/authentication/whitelisting/introduction/" %}}Whitelisting{{%/link%}}: To use the {{%link href="/en/cloud-scale/help/authentication/whitelisting/custom-user-validation/introduction/" %}}Custom User Validation{{%/link%}} feature to employ the custom authentication logic. - {{%link href="/en/cloud-scale/help/authentication/email-templates/introduction/" %}}Email Templates{{%/link%}}: To customize **Email Verification**, and **Forgot Password** email templates. - {{%link href="/en/cloud-scale/help/mail/introduction/" %}}Mail{{%/link%}}: To verify the sender email address and send the email to the required person. - {{%link href="/en/cloud-scale/help/web-client-hosting/introduction/" %}}Web Client Hosting{{%/link%}}: To host the front end of the application. You can initialize and configure the client as a *React app*. 3. {{%bold%}}{{%link href="/en/job-scheduling/" %}}Catalyst Job Scheduling{{%/link%}}{{%/bold%}}<br /> - {{%link href="/en/job-scheduling/help/jobpool/introduction/" %}}Job Pool{{%/link%}}: To execute the function jobs that will trigger the required job function to trigger the sending of the email containing the birthday greeting. - {{%link href="/en/job-scheduling/help/cron/introduction/" %}}Cron{{%/link%}}: To schedule the sending of the greetings email using {{%link href="/en/job-scheduling/help/cron/key-concepts/" %}}Dynamic Cron{{%/link%}}. - {{%link href="/en/job-scheduling/help/job/introduction/" %}}Jobs{{%/link%}}: To trigger the required Job Function when executed from the Job Pool. We will use the {{%link href="https://console.catalyst.zoho.com/" %}}Catalyst web console{{%/link%}} and the {{%link href="/en/cli/v1/cli-command-reference/" %}}Catalyst Command Line Interface{{%/link%}} (CLI) to build this application. {{%note%}}{{%bold%}}Note:{{%/bold%}} You will be given the code for the files to be included in the function and client components in this tutorial. You will just need to copy the provided code and paste it into the appropriate files as directed.{{%/note%}} ### Application Workflow The workflow of the Birthday Greetings application is illustrated below: <br /> 1. You sign up or log in to the application. 2. You can provide a custom message, schedule when you want the birthday greeting to be sent, and to whom it needs to be sent using the UI elements in the client side. 3. The details are then used by the *Advanced I/O function* to create a *Dynamic Cron* using the {{%link href="/en/sdk/nodejs/v2/job-scheduling/cron/create-recurring-cron/" %}}SDK{{%/link%}}. 4. At the scheduled time, the dynamic cron will submit a *function job* to the function job pool, from where the function job will be executed to trigger the *Job Function*. 5. The job function will use the {{%link href="/en/sdk/nodejs/v2/cloud-scale/mail/send-email/" %}}Mail SDK{{%/link%}} to send the email containing the birthday greetings to the required person. The entire workflow of the application will be explained in more detail when you code your {{%link href="/en/tutorials/birthday-greetings/nodejs/config-client/" %}}client logic{{%/link%}}, which we recommend you check out as you follow along the steps. -------------------------------------------------------------------------------- title: "Prerequisites" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.673Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/prerequisites/" service: "All Services" related: - Catalyst CLI Documentation (/en/cli/v1/cli-command-reference/) - Catalyst VS Code Extension (/en/catalyst-extensions/vs-code-extension/introduction/) -------------------------------------------------------------------------------- # Prerequisites Before you begin building the application, you must have the following prerequisites installed on your system: 1. {{%bold%}}Catalyst CLI{{%/bold%}}: Catalyst CLI contains a host of tools that enable you to initialize, develop, test, and deploy the components of your application from your local machine. We will be working with Catalyst CLI in this tutorial. You must perform these actions: - **Install Catalyst CLI**: Catalyst CLI is installed through NPM. You must have NPM and Node.js installed on your system before you install the CLI. Refer to the {{%link href="/en/getting-started/installing-catalyst-cli/" %}}{{%bold%}}Install Catalyst CLI help page{{%/bold%}}{{%/link%}} for details on the prerequisites and the steps to install it. - **Login Catalyst CLI**: After you install Catalyst CLI, you must authenticate the CLI with your Catalyst account before using it. Refer to the {{%link href="/en/cli/v1/login/login-from-cli/" %}}{{%bold%}}CLI Login help page{{%/bold%}}{{%/link%}} for the steps to login from Catalyst CLI and the various options available for it. 2. {{%bold%}}Any IDE tool for Node.js and client code development{{%/bold%}}: You can use any IDE to work with the function and the client code. Some popular choices include Visual Studio Code, IntelliJ IDEA, Eclipse, and Sublime Text. Download and install an IDE of your choice in your system. {{%info image="/images/tutorials/todo-list/vscode.png"%}}If you are a Visual Studio Code IDE user, you can install the {{%bold%}}Catalyst Tools{{%/bold%}} extension, and use your IDE itself in place of the CLI. You can find more details about the Catalyst VS Code extension from this {{%link href="/en/catalyst-extensions/vs-code-extension/introduction/" %}}help section{{%/link%}}.{{%/info%}} -------------------------------------------------------------------------------- title: "" last_updated: "2026-03-18T07:41:08.681Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/sample/" -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Create Project" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.681Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/create-project/" service: "All Services" related: - Catalyst Projects (/en/getting-started/catalyst-projects/) -------------------------------------------------------------------------------- # Create a Project Let’s {{%link href="/en/getting-started/catalyst-projects/#creating-a-catalyst-project" %}}create a Catalyst project{{%/link%}} from the Catalyst console. 1. Log in to the {{%link href="https://console.catalyst.zoho.com/baas/index" %}}Catalyst console{{%/link%}} and click **Create New Project**. <br /> 2. Enter the project’s name as "**BirthdayGreetings**" in the pop-up window. <br /> Your project will be created and opened. <!-- You can open the project by clicking **Access Project**. <br /> --> -------------------------------------------------------------------------------- title: "Create Table" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.682Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/create-table/" service: "All Services" related: - Data Store (/en/cloud-scale/help/data-store/introduction/) - Scopes & Permissions (/en/cloud-scale/help/data-store/scopes-and-permissions/) - Roles (/en/cloud-scale/help/authentication/user-management/roles/introduction/) -------------------------------------------------------------------------------- # Create Table Next, let’s create a table in the {{%link href="/en/cloud-scale/help/data-store/introduction/" %}}Data Store{{%/link%}}. This table is used to create the following columns to store the required details: <table class="content-table"> <thead> <tr> <th class="w25p">Column Name</th> <th class="w25p">Data Type</th> <th class="w50p">Purpose</th> </tr> </thead> <tbody> <tr> <td>{{%badge%}}{{%bold%}}Name{{%/bold%}}{{%/badge%}}</td> <td>{{%badge%}}Var Char{{%/badge%}}</td> <td>To store the name of the people you are going to send wishes to.</td> </tr> <tr> <td>{{%badge%}}{{%bold%}}Message{{%/bold%}}{{%/badge%}}</td> <td>{{%badge%}}Text{{%/badge%}}</td> <td>To store the custom message.</td> </tr> <tr> <td>{{%badge%}}{{%bold%}}BirthDay{{%/bold%}}{{%/badge%}}</td> <td>{{%badge%}}Date{{%/badge%}}</td> <td>To store the date of the birthday.</td> </tr> <tr> <td>{{%badge%}}{{%bold%}}Email{{%/bold%}}{{%/badge%}}</td> <td>{{%badge%}}Var Char{{%/badge%}}</td> <td>To store the recipient's email address.</td> </tr> <tr> <td>{{%badge%}}{{%bold%}}AutoSend{{%/bold%}}{{%/badge%}}</td> <td>{{%badge%}}Boolean{{%/badge%}}</td> <td>To choose to send the email automatically to the required person at the scheduled time. The default value will be {{%bold%}}True{{%/bold%}}.</td> </tr> </tbody> </table> To create a table: 1. Navigate to the Catalyst *Cloud Scale* service section in the console and click **Start Exploring**. <br /> 2. Navigate to the *Data Store* component under the *STORAGE* section and click **Create a new Table**. <br /> 3. Enter the table’s name as "{{%badge%}}BirthDayReminder{{%/badge%}}" and click **Create**. <br /> {{%note%}}{{%bold%}}Note:{{%/bold%}} Ensure that you enter the name exactly as instructed because the application's code contains the same name.{{%/note%}} The table will be created. <br /> Now, let’s create the required columns. {{%info%}}{{%bold%}}Info:{{%/bold%}} You can learn about the various data types supported by Catalyst and the other properties of a column from the {{%link href="/en/cloud-scale/help/data-store/introduction/" %}}Data Store help documentation{{%/link%}}.{{%/info%}} 4. Click **New Column** in the *Schema View* section. <br /> 5. Enter the column’s name as "{{%badge%}}Name{{%/badge%}}". Select the data type as **Var Char**, and enter "{{%badge%}}100{{%/badge%}}" as the **Max Length**. <br /> 6. Click **Create**. <br /> 7. Click the **New Column** button again to create the second column. Name it "{{%badge%}}Message{{%/badge%}}", select **Text** as its data type, and click **Create**. <br /> 8. Click the **New Column** button again to create the third column. Name it "{{%badge%}}BirthDay{{%/badge%}}", select **Date** as its data type, and click **Create**. <br /> 9. Click the **New Column** button again to create the fourth column. Name it "{{%badge%}}Email{{%/badge%}}", select **Var Char** as its data type, enter "{{%badge%}}120{{%/badge%}}" as the **Max Length**, and click **Create**. <br /> 10. Lastly, click the **New Column** button again to create the fifth column. Name it "{{%badge%}}AutoSend{{%/badge%}}", select **Boolean** as its data type, select **True** as the **Default Value**, and click **Create**. <br /> All of the required columns have been created. <br /> ### Configure Scopes and Permissions To allow any user of the Birthday Greetings application to create and manage birthday messages from the client application, you must enable CRUD operations for the *App User* profile and set its {{%link href="/en/cloud-scale/help/authentication/user-management/roles/introduction/" %}}role{{%/link%}} as *User*. To enable the required permissions: 1. Click the **Scopes & Permissions** tab. <br /> 2. Select the **User** profile for the *App User* role. <br /> 3. Select the **Update**, **Insert**, and **Delete** permissions for the *App User* by clicking the respective check boxes. Ensure that you do not change any other permission setting. <br /> The *Data Store* component is now configured for the application. -------------------------------------------------------------------------------- title: "Set up Sender Email Address" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.682Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/setup-mail/" service: "All Services" related: - Mail (/en/cloud-scale/help/mail/introduction/) - Domains (/en/cloud-scale/help/mail/domains/) - Email Configuration (/en/cloud-scale/help/mail/email-configuration/) -------------------------------------------------------------------------------- # Set up Sender Email Address We will be using {{%link href="/en/cloud-scale/help/mail/introduction/" %}}Mail{{%/link%}}, a *Catalyst Cloud Scale* component, to set up the sender email address. This is the address from which the birthday wishes will be sent to the required people. To configure the sender’s email address: 1. Navigate to the **Mail** component under the *NOTIFY* section in the *Cloud Scale* console, and click **Add Email**. <br /> 2. Provide the required details for your sender email address, and click **Add Email**. You can provide an email address that you use. <br /> 3. The email address will be listed in the *Email Configuration* section. <br /> {{%note%}}{{%bold%}}Note:{{%/bold%}} This email address will be the email ID from which the birthday wishes will be sent.{{%/note%}} 4. Click on **Click to confirm** to verify the email address provided. <br /> This action will have automatically sent a Catalyst-generated confirmation code to the email address you provided. <br /> 5. Copy the confirmation code from your email, paste it in the space provided, and click **Confirm**. The email ID will be verified, and the {{%badge%}}Verified{{%/badge%}} status will be indicated under the *Confirmation Status* column. <br /> The sender email address has now been configured. ### Configure the Domain Next, we need to verify the domain we are using to send emails from the application. This verification will ensure that your emails reach your required inbox securely and not in their spam folder. {{%note%}}{{%bold%}}Note:{{%/bold%}} This is a mandatory step to ensure that emails sent from your Catalyst applications are done with strict adherence to DKIM protocols.{{%/note%}} To verify the domain: 1. Navigate to the *Domain* tab and click **Add Domain**. <br /> 2. Enter the sender email address that you wish to configure and click **Create**. <br /> 3. The domain will be listed in the *Domain* tab. Click the **Verify code** button next to the **Yet to verify** option. <br /> This action will have automatically emailed you a verification code generated by Catalyst. <br /> 4. Copy the confirmation code from your email, paste it in the space provided, and click **Confirm**. <br /> The domain will be verified, and the {{%badge%}}Email Verified{{%/badge%}} badge will be added to the domain. <br /> {{%info%}}{{%bold%}}Info:{{%/bold%}} You can learn more about the Domain section and the Mail component from this {{%link href="/en/cloud-scale/help/mail/domains/" %}}help documentation{{%/link%}}.{{%/info%}} The Mail component has now been configured for your application. -------------------------------------------------------------------------------- title: "Enable Authentication" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.682Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/enable-auth/" service: "All Services" related: - Authentication (/en/cloud-scale/help/authentication/introduction/) - Embedded Authentication (/en/cloud-scale/help/authentication/native-catalyst-authentication/embedded-authentication/introduction/) - Public Signup (/en/cloud-scale/help/authentication/public-signup/) - Social Logins (/en/cloud-scale/help/authentication/social-logins/introduction/) - Whitelisting (/en/cloud-scale/help/authentication/whitelisting/introduction/) - Email Templates (/en/cloud-scale/help/authentication/email-templates/introduction/) -------------------------------------------------------------------------------- # Enable Authentication For this tutorial, we will be enabling the Embedded Authentication type to add end users to the application. To enable Embedded Authentication for your application: 1. Navigate to the *Authentication* component under *SECURITY & IDENTITY* and click **Set Up** in the **Native Authentication** option. <br /> 2. Select **Embedded Authentication** and click **Next**. <br /> 3. We will be implementing the script listed in this section when we configure our client. For now, click the **Public Signup** toggle to allow users to sign up to your application. <br /> {{%info%}}{{%bold%}}Info:{{%/bold%}} You can learn more about the {{%bold%}}Public Signup{{%/bold%}} feature from this {{%link href="/en/cloud-scale/help/authentication/public-signup/" %}}help documentation{{%/link%}}.{{%/info%}} 4. Read the information in the pop-up and click **Yes, Proceed**. <br /> 5. *Public Signup* has now been enabled. Your end-users can sign up to your application, and if you choose it, you can allow them to sign up to your application using their Social Logins. Click **Next**. <br /> {{%info%}}{{%bold%}}Info:{{%/bold%}} We will not be covering the Social Logins feature in this tutorial. You can find out more about it in this {{%link href="/en/cloud-scale/help/authentication/social-logins/introduction/" %}}help documentation{{%/link%}}{{%/info%}} 6. We will be skipping the *Additional Settings* section right now. We will revisit the *Custom User Validation* feature present in this section at a later step. Click **Finish**. <br /> Embedded authentication has been enabled for your application. <br /> ### Configure Email Templates **Email Templates** is a feature in *Authentication* that allows you to style and customize the *Email Verification* and *Password Reset* emails that will be sent from your application when an end user attempts to register or reset their password. To configure the required email templates: 1. Click the **Email Templates** tab in the *Authentication* component to navigate to the relevant section. Click **Edit** to customize the **Email Verification** template. <br /> 2. Customize and edit the content. You can also include {{%link href="/en/cloud-scale/help/authentication/email-templates/benefits-and-key-concepts/#placeholders" %}}placeholders{{%/link%}} in the templates for the values that need to be populated dynamically when the emails are invoked. Make the necessary changes and click **Save**. <br /> {{%note%}}{{%bold%}}Notes:{{%/bold%}}<br /> * You can customize the email content to match your preference. * The placeholder content (content present between {{%badge%}}%%{{%/badge%}}) will be dynamically altered and rendered based on the user details. * The {{%badge%}}{{%bold%}}%LINK%{{%/bold%}}{{%/badge%}} placeholder will contain the application’s link that is automatically generated by Catalyst. You can also provide a custom link.{{%/note%}} 3. To customize the Reset Password email template, click the **Password Reset** tab and customize the email content in the same manner as above. <br /> The email templates will now be configured. {{%info%}}{{%bold%}}Info:{{%/bold%}}<br /> * You can learn more about Email Templates from this {{%link href="/en/cloud-scale/help/authentication/email-templates/introduction/" %}}help document{{%/link%}}. * Because we are using Embedded Authentication (a {{%bold%}}Native Authentication{{%/bold%}} type) to authenticate this application, the handling of sending these emails at the required instances will be automatically configured by Catalyst, so you do not have to code the logic to trigger these emails.{{%/info%}} -------------------------------------------------------------------------------- title: "Initialize the Project" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.688Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/init-project/" service: "All Services" related: - Catalyst CLI (/en/cli/v1/cli-command-reference/) - Initialize Resources (/en/cli/v1/initialize-resources/introduction/) - Project Directory (/en/cli/v1/project-directory-structure/introduction/) - Catalyst Functions (/en/serverless/help/functions/introduction/) -------------------------------------------------------------------------------- # Initialize the Project You can now begin working on your Catalyst project from the CLI. The first step is to initialize the project in an empty directory. This will be the home directory of your project, and all of the project files will be saved in it. You can learn more about this from the {{%link href="/en/cli/v1/project-directory-structure/introduction/" %}}Project Directory Structure help page{{%/link%}}. You can learn about initializing a project in detail from the {{%link href="/en/cli/v1/initialize-resources/introduction" %}}CLI help documentation{{%/link%}}. For this application, we will initialize a {{%link href="/en/serverless/help/functions/basic-io/" %}}Basic I/O function{{%/link%}}, which will contain the logic for {{%link href="/en/cloud-scale/help/authentication/whitelisting/custom-user-validation/introduction/" %}}Custom User Validation{{%/link%}} and a {{%link href="/en/cli/v1/initialize-resources/initialize-client/#react-applications" %}}React web app{{%/link%}} as the client component. Later, we will also initialize an {{%link href="/en/serverless/help/functions/advanced-io/" %}}Advanced I/O function{{%/link%}} and a {{%link href="/en/serverless/help/functions/job-functions/" %}}Job Function{{%/link%}}. {{%note%}}{{%bold%}}Note:{{%/bold%}} Ensure that you enter the function's package name or class name exactly as instructed because the application's code contains the same name.{{%/note%}} 1. Create a folder for the project on your local machine and navigate to it from the terminal. 2. Initialize a project by executing the following command from that directory: {{%cli%}}catalyst init{{%/cli%}} 3. Navigate using the **arrow keys**, select your preferred portal, and press the **Enter** key. If you have no other organizations associated with the account, then the default one will be selected automatically. <br /> {{%info%}}{{%bold%}}Info:{{%/bold%}} You can find out more about Catalyst’s multi-org portal feature in this {{%link href="/en/getting-started/catalyst-projects/#access-the-multi-org-portal" %}}help document{{%/link%}}.{{%/info%}} 4. The CLI will now ask you to associate a Catalyst project with the directory. Associate it with the project that we created earlier from the console. Select **BirthdayGreetings** from the list and click **Enter**. <br /> 5. Select **Functions** and **Client** using the **space bar**, then click **Enter** to initialize. <br /> 6. The CLI will initiate the function setup. Select **Basic IO** as the function type to code your *Custom User Validation* function. <br /> 7. Select the latest runtime of **Node.js** as the function stack. <br /> 8. Enter "{{%badge%}}basic_function{{%/badge%}}" as the package name, "{{%badge%}}index.js{{%/badge%}}" as the entry point, and your email address as the author, then press **Enter**. Alternatively, you can press **Enter** without entering inputs to fill in the default values. The CLI will prompt the initialization of the Node dependencies. Press **Y** to confirm the installation, and press **Enter** to confirm your choice. The Node modules will be installed. <br /> The CLI will now initiate the client setup. 9. Select **React web app** and press **Enter** to initialize your client as a {{%link href="/en/cli/v1/initialize-resources/initialize-client/#react-applications" %}}React web app{{%/link%}}. <br /> 10. Select **JavaScript** as the React app type and press **Enter**. <br /> 11. Enter "{{%badge%}}birthdaygreetings{{%/badge%}}" as the name of your client package and click **Enter**. You can also provide any name of your choice. Enter "**y**" at the prompt to install the required React packages, such as "{{%badge%}}react{{%/badge%}}", "{{%badge%}}react-dom{{%/badge%}}," and "{{%badge%}}react-scripts{{%/badge%}}." These packages will be installed through the Catalyst React plugin ({{%link href="https://www.npmjs.com/package/zcatalyst-cli-plugin-react" %}}{{%badge%}}zcatalyst-cli-plugin-react{{%/badge%}}{{%/link%}}). <br /> The client is now successfully initialized as a React web application. <br /> The {{%link href="/en/cli/v1/project-directory-structure/client-directory" %}}client directory{{%/link%}} will be created in the standard structure in the project directory. This is the current structure of the *Birthday Greetings* project’s directory. <br /> Now, we will begin initializing the Advanced I/O Function and Job Function using the following CLI command in the {{%badge%}}BirthdayGreetings/functions/{{%/badge%}} directory: {{%cli%}}catalyst functions:add{{%/cli%}} This {{%link href="/en/cli/v1/working-with-functions/add-functions/" %}}CLI command{{%/link%}} will trigger the function set up and allow you to add the required functions to your project directory. 1. Select the **Advanced IO** function once the function setup starts. <br /> 2. Select the latest runtime of **Node.js** as the function stack. <br /> 3. Enter "{{%badge%}}advance_function{{%/badge%}}" as the package name, "{{%badge%}}index.js{{%/badge%}}" as the entry point, and your email address as the author, then press **Enter**. Alternatively, you can press **Enter** without entering inputs to fill in the default values. The CLI will prompt the initialization of the Node dependencies. Press **Y** to confirm the installation, and press **Enter** to confirm your choice. The Node modules will be installed. <br /> The Advanced I/O function has been initialized. Let's initialize the Job Function using the same CLI command. {{%cli%}}catalyst functions:add{{%/cli%}} 1. Select **Job** as the function type once the function setup starts. <br /> 2. Select the latest runtime of **Node.js** as the function stack. <br /> 3. Enter "{{%badge%}}dynamic_cron{{%/badge%}}" as the package name, "{{%badge%}}index.js{{%/badge%}}" as the entry point, and your email address as the author, then press **Enter**. Alternatively, you can press **Enter** without entering inputs to fill in the default values. The CLI will prompt the initialization of the Node dependencies. Press **Y** to confirm the installation, and press **Enter** to confirm your choice. The Node modules will be installed. <br /> All of the required client and function components have been initialized for your application. This is the current structure of the project directory: <br /> -------------------------------------------------------------------------------- title: "Enable Custom User Validation" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.690Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/enable-custom-logic/" service: "All Services" related: - Authentication (/en/cloud-scale/help/authentication/introduction/) - Authentication SDK (/en/sdk/nodejs/v2/cloud-scale/authentication/get-component-instance/) - Whitelisting (/en/cloud-scale/help/authentication/whitelisting/introduction/) - Custom User Validation (/en/cloud-scale/help/authentication/whitelisting/custom-user-validation/introduction/) -------------------------------------------------------------------------------- # Enable Custom User Validation ### Code Your Custom Authentication Logic Now, we will code our custom logic that needs to be employed when an end user tries to register to your application. The {{%link href="/en/cli/v1/project-directory-structure/functions-directory/" %}}functions directory{{%/link%}}, {{%badge%}}BirthdayGreetings/functions/basic_function/{{%/badge%}} contains: * The {{%badge%}}index.js{{%/badge%}} main function file * The {{%badge%}}catalyst-config.json{{%/badge%}} configuration file * Node modules * {{%link href="https://docs.npmjs.com/files/package.json" %}}{{%badge%}}package.json{{%/badge%}}{{%/link%}} and {{%link href="https://docs.npmjs.com/configuring-npm/package-lock-json.html" %}}{{%badge%}}package-lock.json{{%/badge%}}{{%/link%}} dependency files. You will be adding code in the {{%badge%}}index.js{{%/badge%}} file. The function contains the following functionalities: * The end-user’s details will be provided as a JSON input to the Custom User Validation function. * The function is coded to implement a custom logic based upon which the end-user will either be authenticated or denied. {{%note%}}{{%bold%}}Note:{{%/bold%}} For the purpose of this tutorial, we have coded an example logic for the Custom User Validation function where if the user's email provider is anyone other than Zylker Technology ({{%badge%}}@zylker.com{{%/badge%}}) the end user will not be able to sign up to the application. You can either use the same or code a logic of your preference.{{%/note%}} Copy the code given below and paste it in the {{%badge%}}index.js{{%/badge%}} file in the {{%badge%}}BirthdayGreetings/functions/basic_function{{%/badge%}} directory of your project, and save the file. You can use any IDE of your choice to work with the application’s files. {{%note%}}{{%bold%}}Note:{{%/bold%}} Please go through the code in this section to make sure you fully understand it.{{%/note%}} {{%panel_with_adjustment class="language-javascript line-numbers" header="index.js" footer="button" scroll="set-scroll" %}}const catalyst = require('zcatalyst-sdk-node') module.exports = (context, basicIO) => { const catalystApp = catalyst.initialize(context) const requestDetails = catalystApp.userManagement().getSignupValidationRequest(basicIO) if (requestDetails) { if (requestDetails.user_details.email_id.includes('@zylker.com')) { basicIO.write(JSON.stringify({ status: 'success', user_details: { first_name: requestDetails.user_details.first_name, last_name: requestDetails.user_details.last_name, email_id: requestDetails.user_details.email_id, role_identifier: 'App User', org_id: '' } })) } else { // The user has failed authentication basicIO.write(JSON.stringify({ status: 'failure' })) } } context.close() } {{%/panel_with_adjustment%}} {{%note%}}{{%bold%}}Notes:{{%/bold%}} * Ensure that you change the domain name in line {{%badge%}}{{%bold%}}6{{%/bold%}}{{%/badge%}} with one that matches your requirement. * You can also use this {{%link href="/en/sdk/nodejs/v2/cloud-scale/authentication/custom-user-validation/" %}}SDK{{%/link%}} to code your own custom logic instead of the one implemented above.{{%/note%}} ### Deploy the Function and Configure Custom User Validation To apply this custom logic, we need to deploy this function to the console to ensure that it is available to enable the Custom User Validation option present in the console. To deploy the function, execute the following CLI command to {{%link href="/en/cli/v1/deploy-resources/deploy-options/#--only-lttargetsgt" %}}deploy the function component alone{{%/link%}} to the console. {{%cli%}}catalyst deploy --only functions{{%/cli%}} {{%info%}}{{%bold%}}Info:{{%/bold%}} You can also alternatively choose to deploy just the Custom User Validation function using the following command:<br /> {{%badge%}}catalyst deploy --only functions:function_name{{%/badge%}}{{%/info%}} <br /> Function endpoints will be created for all of the functions. This will allow you to access the functions directly. The functions will be deployed to the console. They can be accessed in the console using the Functions component in the Serverless section of the console. <br /> {{%note%}}{{%bold%}}Note:{{%/bold%}} While we have deployed all of the functions of the project to the console, we will be redeploying them once again after we code the {{%bold%}}Advanced I/O{{%/bold%}} and {{%bold%}}Job functions{{%/bold%}}.{{%/note%}} Now, go to the **Authentication** component present in the *Cloud Scale* section of the console and implement the following steps: 1. Navigate to the **Whitelisting** section in the *Authentication* component, and click the **Custom User Validation** toggle. <br /> 2. Select the **Basic I/O** function from the drop-down in the pop-up. <br /> 3. Click **Configure**. <br /> The Custom User Validation function has been enabled for the project, and the end-users signing up for your application will now be additionally authenticated using this function. {{%note%}}{{%bold%}}Note:{{%/bold%}} You can find out about all of the features present in the Whitelisting section form in this {{%link href="/en/cloud-scale/help/authentication/whitelisting/introduction/" %}}help document{{%/link%}}.{{%/note%}} -------------------------------------------------------------------------------- title: "Configure the Job Scheduling Service" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.694Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/config-jobscheduling/" service: "All Services" related: - Job Scheduling Service (/en/job-scheduling/) - Dynamic Cron (/en/job-scheduling/help/cron/key-concepts/#cron-types) - Job Pool (/en/job-scheduling/help/jobpool/introduction/) - Job Scheduling SDK (/en/sdk/nodejs/v2/job-scheduling/overview/) - Job Scheduling APIs (/en/api/code-reference/job-scheduling/jobpool/get-all-jobpool/#GetAllJobPools) -------------------------------------------------------------------------------- # Configure the Job Scheduling Service Now, we are going to configure the components of the Job Scheduling service to ensure that the required birthday greetings are sent out at the precise moment. First, we are going to create a **Job Pool** to execute our Jobs from. To create a Job Pool: 1. Navigate to the *Job Scheduling* section of the console and click **Start Exploring**. <br /> 2. Navigate to the **Job Pool** component and click **Create Job Pool**. <br /> 3. Enter the name of the Job Pool as "{{%badge%}}MessageTrigger{{%/badge%}}" and select **Function** as the Job Pool type from the drop-down. <br /> 4. Select {{%badge%}}2GB{{%/badge%}} as the **Max Allocated Memory** from the drop-down, and click **Create**. <br /> The Job Pool has been created. <br /> The Job Pool can now be referred to by its name and its ID. The Job Pool is now ready to execute Function Jobs that will trigger the required Job Function. -------------------------------------------------------------------------------- title: "Code the Advanced I/O Function" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.694Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/code-advancedio/" service: "All Services" related: - Advanced I/O Functions (/en/serverless/help/functions/advanced-io/) - Node.js SDK (/en/sdk/nodejs/v2/overview/) -------------------------------------------------------------------------------- # Code the Advanced I/O Function Let's start configuring the Advanced I/O function. This function will be used to communicate with the client and will configure a {{%link href="/en/job-scheduling/help/cron/key-concepts/#cron-types" %}}Dynamic Cron{{%/link%}}. The meta of the Cron will be obtained using the data inputted in the client with the help of the {{%link href="/en/sdk/nodejs/v2/job-scheduling/cron/create-recurring-cron/" %}}Cron creation SDK{{%/link%}}. The Advanced I/O function present in the {{%link href="/en/cli/v1/project-directory-structure/functions-directory/" %}}functions directory{{%/link%}}, {{%badge%}}BirthdayGreetings/functions/advance_function/{{%/badge%}} contains: * The {{%badge%}}index.js{{%/badge%}} main function file * The {{%badge%}}catalyst-config.json{{%/badge%}} configuration file * Node modules * {{%link href="https://docs.npmjs.com/files/package.json" %}}{{%badge%}}package.json{{%/badge%}}{{%/link%}} and {{%link href="https://docs.npmjs.com/configuring-npm/package-lock-json.html" %}}{{%badge%}}package-lock.json{{%/badge%}}{{%/link%}} dependency files. You will be adding code in the {{%badge%}}index.js{{%/badge%}} file. To code this function, you will be using the {{%link href="https://expressjs.com/" %}}Express Node.js framework{{%/link%}}. To import the Express package in the code, you must install the Express dependencies in your system. To install Express.js in your local machine, navigate to the {{%badge%}}BirthdayGreetings/functions/advance_function/{{%/badge%}} in your terminal and execute the following command: {{%cli%}}npm install express --save{{%/cli%}} <br /> Copy the code given below and paste it in the {{%badge%}}index.js{{%/badge%}} file. {{%note%}}{{%bold%}}Note:{{%/bold%}} Please go through the code in this section to make sure you fully understand it.{{%/note%}} {{%panel_with_adjustment class="language-javascript line-numbers" header="index.js" footer="button" scroll="set-scroll" %}}'use strict'; const catalyst = require('zcatalyst-sdk-node'); const express = require('express'); const app = express(); app.use(express.json()); app.post('/insertReminder', async (req, res) => { console.log("Request body:", req.body); try { const catalystApp = catalyst.initialize(req); let userManagement = catalystApp.userManagement(); let userPromise = userManagement.getCurrentUser(); userPromise.then(async (currentUser) => { console.log(currentUser); const datastores = catalystApp.datastore(); const table = datastores.table('12096000001771061'); // Your Table ID await insertReminder(req.body, table, catalystApp); res.status(200).json({ status: 200, message: "Reminder added successfully" }); }); } catch (error) { console.error('Error adding reminder:', error); res.status(500).json({ Error: error.message }); } }); app.get('/getReminder', async (req, res) => { try { const catalystApp = catalyst.initialize(req); const datastores = catalystApp.datastore(); const table = datastores.table('12096000001771061'); // Your Table ID const reminders = await getReminders(catalystApp, table); res.status(200).json(reminders); } catch (error) { console.error('Error retrieving reminders:', error); res.status(500).json({ Error: error.message }); } }); app.put('/updateReminder', async (req, res) => { try { const catalystApp = catalyst.initialize(req); const datastores = catalystApp.datastore(); const table = datastores.table('12096000001771061'); // Your Table ID await updateReminder(req.body, table, catalystApp); res.status(200).json({ status: 200, message: "Reminder updated successfully" }); } catch (error) { console.error('Error updating reminder:', error); res.status(500).json({ Error: error.message }); } }); app.patch('/toggleAutoSend', async (req, res) => { try { const catalystApp = catalyst.initialize(req); const datastores = catalystApp.datastore(); const table = datastores.table('12096000001771061'); // Your Table ID await toggleAutoSend(req.body, table, catalystApp); res.status(200).json({ status: 200, message: "Auto send toggled status successfully" }); } catch (error) { console.error('Error toggling status:', error); res.status(500).json({ Error: error.message }); } }); app.delete('/deleteReminder/:id', async (req, res) => { try { const { id } = req.params; console.log("ID received:", id); const catalystApp = catalyst.initialize(req); const datastores = catalystApp.datastore(); const table = datastores.table('12096000001771061'); // Your Table ID await deleteReminder(id, table, catalystApp); res.status(200).json({ status: 200, message: "Reminder deleted successfully" }); } catch (error) { console.error('Error deleting reminder:', error); res.status(500).json({ Error: error.message }); } }); module.exports = app; async function toggleAutoSend(requestBody, table, catalystApp) { const { id, status } = requestBody; const enableStatus = status === 'enable'; const zcql = catalystApp.zcql(); const query = `SELECT * FROM BirthDayReminder WHERE ROWID = '${id}'`; const response = await zcql.executeZCQLQuery(query); if (!response || response.length === 0) throw new Error('Reminder not found.'); const row = response[0].BirthDayReminder; row.AutoSend = enableStatus; console.log("Row.autosend ", row.AutoSend); const jobScheduling = catalystApp.jobScheduling(); if (!enableStatus) { console.log("false"); await jobScheduling.CRON.deleteCron(`bday_${id.substring(10, 17)}`); } else { console.log("true"); await scheduleCronJob(row, catalystApp); } const updateQuery = `UPDATE BirthDayReminder SET AutoSend = ${enableStatus} WHERE ROWID = '${id}'`; await zcql.executeZCQLQuery(updateQuery); } async function insertReminder(requestBody, table, catalystApp) { const { name, birthday, message, email } = requestBody; if (!name || !birthday || !message || !email) throw new Error('Missing required fields.'); const row = { Name: name, BirthDay: birthday, Message: message, Email: email }; try { const insertedRow = await table.insertRow(row); await scheduleCronJob(insertedRow, catalystApp); } catch (error) { console.error('Error inserting reminder:', error); throw error; } } async function getReminders(catalystApp, table) { try { const zcql = catalystApp.zcql(); const query = 'SELECT * FROM BirthDayReminder'; const response = await zcql.executeZCQLQuery(query); if (!Array.isArray(response)) throw new Error('Invalid response from ZCQL query'); return response.map(row => { const reminder = row.BirthDayReminder; return { ID: reminder.ROWID || 'N/A', Name: reminder.Name || 'N/A', BirthDay: reminder.BirthDay || 'N/A', Message: reminder.Message || 'N/A', Email: reminder.Email || 'N/A', AutoSend: reminder.AutoSend || false }; }); } catch (error) { console.error('Error retrieving reminders:', error.message); throw error; } } async function updateReminder(requestBody, table, catalystApp) { const { ID, Name, BirthDay, Message, Email } = requestBody; const jobScheduling = catalystApp.jobScheduling(); if (!ID || !Name || !BirthDay || !Message || !Email) throw new Error('Missing required fields.'); const zcql = catalystApp.zcql(); const query = `SELECT AutoSend FROM BirthDayReminder WHERE ROWID = '${ID}'`; const response = await zcql.executeZCQLQuery(query); const AutoSend = response[0]?.BirthDayReminder?.AutoSend || false; const row = { ROWID: ID, Name: Name, BirthDay: BirthDay, Message: Message, Email: Email }; if (AutoSend) { await jobScheduling.CRON.deleteCron(`bday_${ID.substring(10, 17)}`); try { await table.updateRow(row); await scheduleCronJob(row, catalystApp); } catch (error) { console.error('Error updating reminder:', error); throw error; } } else { try { await table.updateRow(row); console.log('Row updated:', row); } catch (error) { console.error('Error updating reminder:', error); throw error; } } } async function deleteReminder(id, table, catalystApp) { if (!id) throw new Error('ID is missing.'); const zcql = catalystApp.zcql(); const query = `SELECT AutoSend FROM BirthDayReminder WHERE ROWID = '${id}'`; const response = await zcql.executeZCQLQuery(query); const AutoSend = response[0]?.BirthDayReminder?.AutoSend || false; const jobScheduling = catalystApp.jobScheduling(); if (AutoSend) { await jobScheduling.CRON.deleteCron(`bday_${id.substring(10, 17)}`); } await table.deleteRow(id); console.log('Reminder deleted:', id); } async function scheduleCronJob(row, catalystApp) { const jobScheduling = catalystApp.jobScheduling(); const { ROWID, BirthDay, Name, Email, Message } = row; const dob = new Date(BirthDay); const cronDetail = { cron_name: `bday_${ROWID.substring(10, 17)}`, description: `Birthday reminder for ${Name}`, cron_status: true, cron_type: 'CALENDAR', cron_detail: { hour: 0, minute: 0, second: 0, days: [dob.getDate()], months: [dob.getMonth()], repetition_type: 'YEARLY' }, job_meta: { job_name: `bday_${ROWID.substring(10, 17)}`, jobpool_name: 'Trigger_Message', // Your job pool name jobpool_id: '12096000001771827', // Your Job pool ID target_type: 'FUNCTION', target_name: 'dynamic_cron', // Your job function name job_service_identifier: 'default', retries: 2, retry_interval: 900, params: { id: ROWID, name: Name, email: Email, message: Message, birthday: BirthDay } } }; try { const yearlyCronDetails = await jobScheduling.CRON.createCron(cronDetail); console.log('Yearly cron created:', yearlyCronDetails); } catch (error) { console.error('Error scheduling cron job:', error); throw error; } } {{%/panel_with_adjustment%}} {{%note%}}{{%bold%}}Notes:{{%/bold%}} * Ensure that you provide your {{%bold%}}Table ID{{%/bold%}} in lines {{%badge%}}{{%bold%}}15{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}33{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}47{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}64{{%/bold%}}{{%/badge%}}, and {{%badge%}}{{%bold%}}85{{%/bold%}}{{%/badge%}}. You can fetch this value for the table you created from the Data Store. * Ensure that you provide with your job meta details in lines {{%badge%}}{{%bold%}}245{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}246{{%/bold%}}{{%/badge%}}, and {{%badge%}}{{%bold%}}248{{%/bold%}}{{%/badge%}}. You can fetch these from the Job Pool section in Job Scheduling.{{%/note%}} Let's go over the APIs that were used to code the Advanced I/O function to handle the routing between the server, the Data Store, and the Job Scheduling service: * {{%badge%}}{{%bold%}}POST /insertReminder{{%/bold%}}{{%/badge%}}: Will add a new reminder to the Data Store with the details provided in the client. Will also schedule a dynamic cron job for the reminder using the Catalyst Job Scheduling service to automate tasks. * {{%badge%}}{{%bold%}}GET /getReminder{{%/bold%}}{{%/badge%}}: Will retrieve all of the reminders stored in the Data Store and return them as the response. * {{%badge%}}{{%bold%}}PUT /updateReminder{{%/bold%}}{{%/badge%}}: Will update an existing reminder in the Data Store with the details provided in the client. * {{%badge%}}{{%bold%}}PATCH /toggleAutoSend{{%/bold%}}{{%/badge%}}: Will toggle the auto-send status of a reminder based on the preference set by the user in the client side. * {{%badge%}}{{%bold%}}DELETE /deleteReminder/:id{{%/bold%}}{{%/badge%}}: Will delete a specific reminder from the Data Store and Cron using the provided {{%badge%}}reminder ID{{%/badge%}}. -------------------------------------------------------------------------------- title: "Code the Job Function" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.696Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/code-job-func/" service: "All Services" related: - Job Functions (/en/serverless/help/functions/job-functions/) - Job Scheduling SDK (/en/sdk/nodejs/v2/job-scheduling/overview/) - Job Scheduling API (/en/api/code-reference/job-scheduling/jobpool/get-all-jobpool/#GetAllJobPools) -------------------------------------------------------------------------------- # Code the Job Function Now let's code the job function that will be triggered when a Function Job is executed from the Function Job Pool. This function will be used to send out the birthday greeting to the required person via their email. This function will be employing the {{%link href="/en/sdk/nodejs/v2/cloud-scale/mail/send-email/" %}}Mail SDK{{%/link%}}. The job function present in the {{%link href="/en/cli/v1/project-directory-structure/functions-directory/" %}}functions{{%/link%}} directory, {{%badge%}}BirthdayGreetings/functions/dynamic_cron/{{%/badge%}} contains: * The {{%badge%}}index.js{{%/badge%}} main function file * The {{%badge%}}catalyst-config.json{{%/badge%}} configuration file * Node modules * {{%link href="https://docs.npmjs.com/files/package.json" %}}{{%badge%}}package.json{{%/badge%}}{{%/link%}} and {{%link href="https://docs.npmjs.com/configuring-npm/package-lock-json.html" %}}{{%badge%}}package-lock.json{{%/badge%}}{{%/link%}} dependency files. You will be adding code in the {{%badge%}}index.js{{%/badge%}} file. Copy the code given below and paste it in the {{%badge%}}index.js{{%/badge%}} file. {{%note%}}{{%bold%}}Note:{{%/bold%}} Please go through the code in this section to make sure you fully understand it.{{%/note%}} {{%panel_with_adjustment class="language-javascript line-numbers" header="index.js" footer="button" scroll="set-scroll" %}}'use strict'; const catalyst = require('zcatalyst-sdk-node'); module.exports = async (jobRequest, context) => { const catalystApp = catalyst.initialize(context); try { console.log("Jobs", jobRequest.getAllJobParams()); const { id, name, email, message, birthday } = jobRequest.getAllJobParams(); await catalystApp.email().sendMail({ from_email: 'emmy@zylker.com', // Add sender's email address to_email: [email], html_mode: true, subject: `Birthday Wishes for ${name}`, content: `Hello ${name},<br><br>${message}`, }); console.log(`Email sent successfully to ${email}`); context.closeWithSuccess('Email sent successfully.'); } catch (error) { console.error('Error:', error); context.closeWithFailure('Failed to send email.'); } }; {{%/panel_with_adjustment%}} {{%note%}}{{%bold%}}Note:{{%/bold%}} Ensure that you add the sender email address you configured using the Mail component in line {{%badge%}}{{%bold%}}15{{%/bold%}}{{%/badge%}}.{{%/note%}} All of the required functions have now been configured for your application. -------------------------------------------------------------------------------- title: "Configure the Client" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.696Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/config-client/" service: "All Services" related: - Client Directory (/en/cli/v1/project-directory-structure/client-directory) -------------------------------------------------------------------------------- # Configure the Client Let's configure the client component. The React web {{%link href="/en/cli/v1/project-directory-structure/client-directory" %}}client directory{{%/link%}} contains the following files: - The root directory of the client contains a {{%badge%}}client-package.json{{%/badge%}} file, which is a configuration file defining the name, version, default home page, and redirect page of the client component. - The {{%badge%}}birthdaygreetings{{%/badge%}} client directory contains two subfolders, per the default project structure of a React app: - The {{%badge%}}public{{%/badge%}} folder is generally used to hold files that can be openly accessed by browsers through public URLs, such as icon files of the web app, the {{%badge%}}index.html{{%/badge%}} file. - The {{%badge%}}src{{%/badge%}} folder contains the application’s source files that will be included in the build folder when we compile the React app. - The client directory also contains the {{%link href="https://docs.npmjs.com/files/package.json" %}}{{%badge%}}package.json{{%/badge%}}{{%/link%}} dependency file and a hidden {{%link href="https://docs.npmjs.com/files/package.json" %}}{{%badge%}}.gitignore{{%/badge%}}{{%/link%}} file. The files in the {{%badge%}}BirthdayGreetings/birthdaygreetings/public{{%/badge%}} directory include: - {{%link href="https://reactjs.org/docs/getting-started.html#try-react" %}}Native React files{{%/link%}} such as {{%badge%}}Tests.js{{%/badge%}}, {{%badge%}}index.js{{%/badge%}}, {{%badge%}}reportWebVitals.js{{%/badge%}}, {{%badge%}}logo.svg{{%/badge%}}, and {{%badge%}}App.test.js{{%/badge%}}. - {{%badge%}}{{%bold%}}index.html{{%/bold%}}{{%/badge%}} Contains a Catalyst web SDK that allows you to access Catalyst components. - Additionally, we will be creating the following files: - {{%badge%}}{{%bold%}}embeddediframe.css{{%/bold%}}{{%/badge%}}: Will contain the styling elements required for the login forms present in your application. - {{%badge%}}{{%bold%}}fpwd.css{{%/bold%}}{{%/badge%}}: Will contain the styling elements required for the forgot password screens present in your application. The files in the {{%badge%}}BirthdayGreetings/birthdaygreetings/src{{%/badge%}} directory include: - Native React files such as {{%badge%}}setupTests.js{{%/badge%}}, {{%badge%}}reportWebVitals.js{{%/badge%}}, {{%badge%}}logo.svg{{%/badge%}}, and {{%badge%}}App.test.js{{%/badge%}}. - {{%badge%}}{{%bold%}}App.js{{%/bold%}}{{%/badge%}}: Will contain the hash routes to your application. - {{%badge%}}{{%bold%}}App.css{{%/bold%}}{{%/badge%}}: Will contain the overall styling elements of your application. - {{%badge%}}{{%bold%}}index.js{{%/bold%}}{{%/badge%}}: Will act as the entry point of your application. - {{%badge%}}{{%bold%}}index.css{{%/bold%}}{{%/badge%}}: Contains the styling of the elements present in the {{%badge%}}index.js{{%/badge%}} file. - We will be adding the following additional files to the {{%badge%}}src{{%/badge%}} directory: - {{%badge%}}{{%bold%}}Layout.js{{%/bold%}}{{%/badge%}}: Will contain the React component for the application's logic. - {{%badge%}}{{%bold%}}LoginPage.js{{%/bold%}}{{%/badge%}}: Will contain the React component for the login pages you generated in the Catalyst console. - {{%badge%}}{{%bold%}}UserProfile.js{{%/bold%}}{{%/badge%}}: Will contain the user profile page, which displays a top bar and a form for submitting reminder data to the server. - {{%badge%}}{{%bold%}}UserProfile.css{{%/bold%}}{{%/badge%}}: Will contain the styling elements used for the user profile page, including custom scroll bar settings and layout adjustments for the header section. - {{%badge%}}{{%bold%}}Signup.js{{%/bold%}}{{%/badge%}}: Will contain the React component for the Signup pages you generated in the Catalyst console. - {{%badge%}}{{%bold%}}Signup.css{{%/bold%}}{{%/badge%}}: Will contain the styling elements for the Signup page in the application. - {{%badge%}}{{%bold%}}ListUsers.js{{%/bold%}}{{%/badge%}}: Will contain the page that lists out all of the reminders you have set up, allows editing and deletion, and provides the ability to toggle reminder status using Axios requests. - Additionally, you need to create a folder called {{%badge%}}{{%bold%}}components{{%/bold%}}{{%/badge%}} in the {{%badge%}}src{{%/badge%}} directory. The {{%badge%}}BirthdayGreetings/birthdaygreetings/src/components/{{%/badge%}} will include the following code files: - {{%badge%}}{{%bold%}}form.jsx{{%/bold%}}{{%/badge%}}: Will contain the form component responsible for managing form data, handling input changes, and submitting the form data to the parent component via the {{%badge%}}onSubmit{{%/badge%}} function. - {{%badge%}}{{%bold%}}modal.jsx{{%/bold%}}{{%/badge%}}: Will contain the modal functionality for editing user data, including input fields for name, message, birthday, and email, along with an update button. - {{%badge%}}{{%bold%}}modal.css{{%/bold%}}{{%/badge%}}: Will contain the styling for the modal component, including layout, input fields, and buttons for editing user data with a backdrop and centered content. - {{%badge%}}{{%bold%}}ToggleSwitch.jsx{{%/bold%}}{{%/badge%}}: Will contain the logic for the toggle switch component, allowing users to toggle between two states, such as enabling or disabling a feature. - {{%badge%}}{{%bold%}}toggleSwitch.css{{%/bold%}}{{%/badge%}}: Will contain the styling for a sliding toggle switch with transition effects for the checked and unchecked states. - {{%badge%}}{{%bold%}}TopBar.jsx{{%/bold%}}{{%/badge%}}: Will contain the structure of the top navigation bar, including links to pages like "{{%badge%}}Add User{{%/badge%}}" and "{{%badge%}}List User{{%/badge%}}," a title in the center, and a "{{%badge%}}Sign-out{{%/badge%}}" button on the right. - {{%badge%}}{{%bold%}}topBar.css{{%/bold%}}{{%/badge%}}: Will contain the styling elements required to style the top navigation bar, including layout, button appearance, and sticky positioning. - {{%badge%}}{{%bold%}}UserList.jsx{{%/bold%}}{{%/badge%}}: Will contain the structure for rendering a list of reminders with the option to toggle, edit, or delete each reminder. - {{%badge%}}{{%bold%}}Userlist.css{{%/bold%}}{{%/badge%}}: Will contain the styling elements required to style the user list, including layout, item styling. Will also contain the button designs for edit and delete actions. We will be coding the {{%badge%}}{{%bold%}}index.html{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}embeddediframe.css{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}fpwd.css{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}App.js{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}App.css{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}index.css{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}index.js{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}Layout.js{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}LoginPage.js{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}UserProfile.css{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}UserProfile.js{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}Signup.js{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}Signup.css{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}form.jsx{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}Form.css{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}modal.jsx{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}modal.css{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}ToggleSwitch.jsx{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}toggleSwitch.css{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}TopBar.jsx{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}topBar.css{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}UserList.jsx{{%/bold%}}{{%/badge%}}, and {{%badge%}}{{%bold%}}Userlist.css{{%/bold%}}{{%/badge%}} files. We will also be modifying the {{%badge%}}{{%bold%}}client-package.json{{%/bold%}}{{%/badge%}} to ensure that the homepage of the application is the login page. ### Install Additional React Packages Before you begin adding code to your files, you need to install the following packages: * {{%badge%}}react-router-dom{{%/badge%}}: This package is required to perform hash routing. * {{%badge%}}axios{{%/badge%}}: To smoothly handle client promises. Install the {{%badge%}}react-router-dom{{%/badge%}} package by executing the following command in the {{%badge%}}BirthdayGreetings/birthdaygreetings/public/{{%/badge%}} directory using your terminal: {{%cli%}}npm install react-router-dom{{%/cli%}} <br /> Install the {{%badge%}}axios{{%/badge%}} package by executing the following command in the {{%badge%}}BirthdayGreetings/birthdaygreetings/public/{{%/badge%}} directory using your terminal: {{%cli%}}npm install axios{{%/cli%}} <br /> This is the project directory after you have created the required files and deleted the unnecessary ones. <br /> Copy the code given below and paste it in the respective files located in the client directory ({{%badge%}}birthdaygreetings/public/{{%/badge%}}) using an IDE and save the file. {{%panel_with_adjustment class="language-xml line-numbers" header="index.html" footer="button" scroll="set-scroll" %}}&lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="utf-8" /&gt; &lt;link rel="icon" href="%PUBLIC_URL%/favicon.ico" /&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1" /&gt; &lt;meta name="theme-color" content="#000000" /&gt; &lt;meta name="description" content="Web site created using create-react-app" /&gt; &lt;link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /&gt; &lt;link rel="manifest" href="%PUBLIC_URL%/manifest.json" /&gt; &lt;script src="https://static.zohocdn.com/catalyst/sdk/js/4.5.0/catalystWebSDK.js"&gt; &lt;/script&gt; &lt;script src="/__catalyst/sdk/init.js"&gt; &lt;/script&gt; &lt;title&gt;React App&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;noscript&gt;You need to enable JavaScript to run this app.&lt;/noscript&gt; &lt;div id="root"&gt; &lt;/div&gt; &lt;/body&gt; &lt;/html&gt; {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-css line-numbers" header="embeddediframe.css" footer="button" scroll="set-scroll" %}}@font-face { font-family: "Roboto Mono"; font-weight: 400; font-style: normal; src: url("https://webfonts.zohowebstatic.com/robotomonoregular/font.eot"); src: url("https://webfonts.zohowebstatic.com/robotomonoregular/font.eot?#iefix") format("eot"), url("https://webfonts.zohowebstatic.com/robotomonoregular/font.woff2") format("woff2"), url("https://webfonts.zohowebstatic.com/robotomonoregular/font.woff") format("woff"), url("https://webfonts.zohowebstatic.com/robotomonoregular/font.ttf") format("truetype"), url("https://webfonts.zohowebstatic.com/robotomonoregular/font.svg#RobotoMono-Regular") format("svg"); } @font-face { font-family: "LatoLgt"; font-weight: 300; font-style: normal; src: url("https://webfonts.zohowebstatic.com/latolight/font.eot"); src: url("https://webfonts.zohowebstatic.com/latolight/font.eot?#iefix") format("eot"), url("https://webfonts.zohowebstatic.com/latolight/font.woff2") format("woff2"), url("https://webfonts.zohowebstatic.com/latolight/font.woff") format("woff"), url("https://webfonts.zohowebstatic.com/latolight/font.ttf") format("truetype"), url("https://webfonts.zohowebstatic.com/latolight/font.svg#Lato-Light") format("svg"); } @font-face { font-family: "LatoReg"; font-weight: 400; font-style: normal; src: url("https://webfonts.zohowebstatic.com/latoregular/font.eot"); src: url("https://webfonts.zohowebstatic.com/latoregular/font.eot?#iefix") format("eot"), url("https://webfonts.zohowebstatic.com/latoregular/font.woff2") format("woff2"), url("https://webfonts.zohowebstatic.com/latoregular/font.woff") format("woff"), url("https://webfonts.zohowebstatic.com/latoregular/font.ttf") format("truetype"), url("https://webfonts.zohowebstatic.com/latoregular/font.svg#Lato-Regular") format("svg"); } @font-face { font-family: "LatoSB"; font-weight: 700; font-style: normal; src: url("https://webfonts.zohowebstatic.com/latobold/font.eot"); src: url("https://webfonts.zohowebstatic.com/latobold/font.eot?#iefix") format("eot"), url("https://webfonts.zohowebstatic.com/latobold/font.woff2") format("woff2"), url("https://webfonts.zohowebstatic.com/latobold/font.woff") format("woff"), url("https://webfonts.zohowebstatic.com/latobold/font.ttf") format("truetype"), url("https://webfonts.zohowebstatic.com/latobold/font.svg#Lato-Bold") format("svg"); } @font-face { font-family: "LatoB"; font-weight: 900; font-style: normal; src: url("https://webfonts.zohowebstatic.com/latoblack/font.eot"); src: url("https://webfonts.zohowebstatic.com/latoblack/font.eot?#iefix") format("eot"), url("https://webfonts.zohowebstatic.com/latoblack/font.woff2") format("woff2"), url("https://webfonts.zohowebstatic.com/latoblack/font.woff") format("woff"), url("https://webfonts.zohowebstatic.com/latoblack/font.ttf") format("truetype"), url("https://webfonts.zohowebstatic.com/latoblack/font.svg#Lato-Black") format("svg"); } @font-face { font-family: "signinicon"; src: url(/images/signinicon.eot); src: url(/images/signinicon.eot) format("embedded-opentype"), url(/__catalyst/auth/static-file?file_name=signinicon.woff2) format("woff2"), url(/__catalyst/auth/static-file?file_name=signinicon.ttf) format("truetype"), url(/__catalyst/auth/static-file?file_name=signinicon.woff) format("woff"), url("../images/fonts/signinicon.651f82b8f8f51f33cd0b6331f95b5b4f.svg") format("svg"); font-weight: normal; font-style: normal; font-display: block; } [class^="icon-"], [class*=" icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ font-family: "signinicon" !important; speak: never; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-hide:before { content: "\e907"; } .icon-show:before { content: "\e914"; } .icon-backarrow:before { content: "\e900"; } .icon-backup:before { content: "\e901"; } .icon-support:before { content: "\e915"; } body { font-family: "Roboto", sans-serif; margin: 0px; } .bg_one { display: block; position: fixed; top: 0px; left: 0px; height: 100%; width: 100%; background: url("../images/bg.49756b7c711696d95133fa95451f8e13.svg") transparent; background-size: auto 100%; z-index: -1; } .signin_container { height: 25rem; display: block; width: 520px; background-color: #fff; box-shadow: 0px 2px 30px #ccc6; margin: auto; position: relative; z-index: 1; margin-top: auto; overflow: hidden; } .mod_container { width: 500px; } .checkbox_label { display: inline-block; font-size: 14px; margin: 0px 6px; float: left; cursor: pointer; line-height: 20px; } .signin_box { min-height: 520px; height: auto; background: #fff; box-sizing: border-box; border-radius: 2px; transition: all 0.1s ease-in-out; float: left; overflow-y: auto; display: table-cell; border-right: 2px solid #f1f1f1; width: 100%; padding: 0px 30px; height: auto; border-right: none; } .nopassword_message { display: table-cell; font-size: 14px; color: #222222; letter-spacing: 0px; line-height: 20px; height: auto; width: auto; margin-left: 10px; font-weight: 400; padding-left: 10px; } .portal_logo { display: block; height: 30px; width: auto; margin-bottom: 20px; background-size: auto 100%; } .pointer { cursor: pointer; } #forgotpassword { cursor: default; } #forgotpassword > a { cursor: pointer; } .text16 { display: block; text-align: center; margin-bottom: 30px; color: #626262; font-size: 16px; font-weight: 500; text-decoration: none; } .checkbox { float: left; z-index: 0; height: 12px; width: 12px; display: inline-block; border: 2px solid #ccc; border-radius: 2px; position: relative; top: 1.5px; overflow: hidden; background-color: #fff; } .show_hide_password { font-size: 34px; color: #a7a7a7; position: relative; top: -39px; right: 13px; float: right; cursor: pointer; height: 20px; } .pass_policy, .pass_policy_error { color: #8c8c8c; font-size: 14px; padding-top: 10px; } .pass_policy_error { color: #ed7500; display: block; } .checkbox_check { position: absolute; z-index: 1; opacity: 0; left: 50px; margin: 0px; height: 16px; cursor: pointer; width: 16px; } .checkbox_div { display: none; height: 16px; width: auto; margin-bottom: 30px; } .checkbox_div { display: block; } #terminate_session_form .checkbox_mod { margin-top: 50px; } .checkbox_tick { display: block; height: 3px; width: 8px; border-bottom: 2px solid #fff; border-left: 2px solid #fff; transform: rotate(-45deg); margin: 2px 1px; } .passexpsuccess { display: none; height: 106px; width: 178px; background: url("../images/passexpiry.db4c66debd4dd8880655f338ead6b973.png") no-repeat transparent; background-size: auto 100%; margin: 0px auto; margin-bottom: 20px; } .signin_head { display: block; font-size: 24px; font-weight: 500; margin-bottom: 30px; line-height: 30px; transition: all 0.1s ease-in-out; } .rightside_box { width: 390px; height: auto; float: right; box-sizing: border-box; padding: 40px; background-color: #fff; display: table-cell; } .service_name, .backup_desc, .pass_name { display: block; font-size: 16px; color: #000; font-weight: 400; } .titlename { display: block; font-size: 24px; color: #000; font-weight: 400; text-transform: capitalize; width: auto; margin-bottom: 20px; } .extramargin { margin-top: 10px; } .show_hide_password { font-size: 34px; color: #a7a7a7; position: relative; top: -39px; right: 13px; float: right; cursor: pointer; height: 20px; } .textbox_div { display: block; margin-bottom: 30px; position: relative; } .textbox_label { display: block; font-size: 14px; color: #626262; padding-bottom: 10px; } .textbox { display: block; width: 100%; height: 44px; box-sizing: border-box; border-radius: 2px; text-indent: 12px; font-size: 16px; outline: none; border: none; padding-right: 12px; transition: all 0.2s ease-in-out; background: #f1f1f1; border: 1px solid #e4e4e4; } .textbox:valid { border: 2px solid #f4f6f8; } .textindent42 { text-indent: 36px; } .textindent62 { text-indent: 56px; } .textintent52 { text-indent: 46px; } ::-webkit-credentials-auto-fill-button { visibility: hidden; pointer-events: none; position: absolute; right: 0px; } input[type="text"], input[type="password"], input[type="email"], input[type="number"] { -webkit-appearance: none; -moz-appearance: none; appearance: none; } .errorborder:focus, .textbox.errorborder { border-bottom: 2px solid #ff8484; } .btn { cursor: pointer; display: block; width: 100%; height: 44px; border-radius: 4px; letter-spacing: 0.5; font-size: 14px; font-weight: 600; outline: none; border: none; margin: auto; text-transform: uppercase; margin-bottom: 30px; transition: all 0.2s ease-in-out; } .signupbtn { width: 250px; margin-left: 0px; } .blue { box-shadow: 0px 2px 2px #fff; background-color: #159aff; color: #fff; } .grey { background-color: #f3f3f3; color: #3a3a3a; letter-spacing: 0px; } .green { box-shadow: 0px 2px 2px #fff; background-color: #00c573; color: #fff; } .blue:hover { background-color: #0966c2; cursor: pointer; box-shadow: 0px 2px 2px rgba(90, 183, 254, 0.2); } .changeloadbtn { display: block; height: 44px; width: 44px; border-radius: 22px; padding: 0px; } .changeloadbtn:before { content: ""; height: 20px; width: 20px; display: inline-block; margin: 9px; border: 3px solid #fff; border-top: 3px solid #5ab7fe; border-radius: 50%; animation: spin 0.5s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .hellouser { margin-bottom: 30px; } .username { display: inline-block; max-width: 100%; font-size: 16px; font-weight: 500; line-height: 24px; overflow: hidden; text-overflow: ellipsis; padding-right: 10px; float: left; } .Notyou { display: inline-block; font-size: 16px; line-height: 24px; } .signup_text { text-align: center; margin: 20px 0px; font-size: 16px; } .backbtn { display: none; float: left; height: 28px; width: 28px; border-radius: 50%; position: absolute; padding: 0px 5px; cursor: pointer; overflow: hidden; transition: all 0.1s ease; } .zeroheight { width: 0px; height: 0px; overflow: hidden; display: block; } #captcha, #bcaptcha { background: #fff; width: 60%; display: inline-block; } #captcha_img, #bcaptcha_img { width: 40%; float: right; background-color: #ffffff; border-left: none; } #captcha_img img, #bcaptcha_img img { height: 40px; } .reloadcaptcha { float: right; clear: both; margin-top: -37px; height: 30px; width: 30px; border-radius: 50px; background: url("../images/reload.eaef7ea18b680bc07558164c918909a6.png") no-repeat transparent 5px; background-size: 50%; display: inline-block; cursor: pointer; } #captcha_container { display: none; } .options { display: block; width: 100%; max-width: 500px; min-height: 80px; padding: 0px 50px; cursor: pointer; } .options:hover { background-color: #f9f9f9; } .option_details { display: inline-block; height: auto; width: 320px; margin-left: 15px; } .option_title { display: block; font-size: 16px; font-weight: 500; } .img_option { display: table-cell; height: 24px; width: 24px; color: #499aff; font-size: 24px; vertical-align: top; } .errorlabel { color: #e92b2b; } .fielderror { display: none; font-size: 14px; margin-top: 10px; } .fielderror a { text-decoration: none; color: #309ff4; } .bluetext { color: #309ff4; cursor: pointer; } #country_code_select { width: 50px; height: 40px; position: absolute; opacity: 0; display: none; z-index: 0; } .select_country_code { width: 50px; height: 40px; display: inline-block; float: left; position: absolute; line-height: 40px; text-align: center; font-size: 16px; color: black; display: none; z-index: 0; } .pic { width: 20px; height: 14px; background-size: 280px 252px; background-image: url("../images/Flags2x.0b8394efb0ea9167cef2465fb8f63d78.png"); float: left; } .cc { float: right; color: #aeaeae; } .cn { margin-left: 10px; } .select2-results__option--highlighted { background: #f4f6f8; } .searchparent { height: auto; } .searchlbl { font-size: 10px; font-weight: bolder; display: inline-block; margin-top: 15px; margin-bottom: 10px; margin-left: 10px; } .cntrysearch { width: 380px; height: 32px; border-radius: 2px; font-size: 14px; box-sizing: border-box; padding: 8px 10px; background: #f7f7f7; outline: none; border: none; margin-top: 10px; } .select2-results__option { list-style-type: none; height: 40px; box-sizing: border-box; padding: 12px 20px; font-size: 14px; line-height: 16px; } .select2-search__field { width: 380px; height: 32px; border: none; outline: none; background: #f7f7f7; border-radius: 2px; margin: 10px 0 0 10px; font-size: 14px; padding: 10.5px 8px; } .select2-selection { display: inline-block; outline: none; /* background-color: #F4F6F8; */ height: 40px; text-align: center; padding: 10px; box-sizing: border-box; cursor: pointer; } .selection { transition: all 0.2s ease-in-out; -webkit-user-select: none; display: inline-block; white-space: nowrap; overflow: hidden; width: 0px; height: 43px; } #select2-combobox-results { padding-left: 0px; max-height: 200px; overflow-y: scroll; overflow-x: hidden; width: 400px; margin-top: 10px; margin-bottom: 0px; background: white; } .select2-container--open { z-index: 10; background: #ffffff; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.13); width: auto; box-sizing: border-box; } .select2 { position: absolute; background: transparent; box-shadow: none; display: none; margin: 2px; } .select2-results__options { overflow-y: auto; max-height: 200px; } .selection { width: auto; width: -moz-fit-content; width: -webkit-fit-content; margin: auto; display: block; } .select2-search–-hide, .select2-selection__clear { display: none; } #otp_container, #enableotpoption, #mfa_ga_container, #mfa_otp_container, #mfa_totp_container, #recoverybtn, #recovery_container, #enableforgot, #enablesaml, .trustbrowser_ui, #backup_ver_container, #backup_container, #enablemore, .password_expiry_container, .terminate_session_container { display: none; } .errorlabel { color: #e92b2b; } .fielderror { display: none; font-size: 14px; margin-top: 10px; } .field_error { color: #f37272; font-size: 14px; padding-top: 10px; } #recoverybtn, #problemsignin, .tryanother { position: absolute; left: 0px; right: 0px; bottom: 40px; } .textbox_actions, .textbox_actions_saml, .textbox_actions_more { display: block; margin-top: 20px; height: 20px; } .bluetext_action { display: inline-block; float: left; font-size: 14px; color: #159aff; font-weight: 500; line-height: 16px; cursor: pointer; } .bluetext_action a { text-decoration: none; } .bluetext_action_right { float: right; } .textbox_actions { display: block; margin-top: 20px; height: 20px; } .Alert, .Errormsg { display: block; margin: auto; height: auto; min-width: 200px; width: fit-content; width: -moz-fit-content; max-width: 600px; background-color: #032239; border-radius: 6px; position: fixed; top: -100px; left: 0px; right: 0px; transition: all 0.2s ease; padding: 0px 20px; z-index: 2; } .tick_icon, .error_icon { display: inline-block; height: 20px; width: 20px; background-color: #22c856; border-radius: 50%; background-size: 60px; margin: 15px; float: left; } .tick_icon:after { display: block; content: ""; height: 5px; width: 9px; border-bottom: 2px solid #fff; border-left: 2px solid #fff; transform: rotate(-45deg); margin: 7px 6px; box-sizing: border-box; } .alert_message, .error_message { display: inline-block; font-size: 14px; color: #fff; line-height: 18px; margin: 16px 0px; margin-right: 20px; max-width: 510px; } .error_icon { background-color: #ff676f; } .error_icon:before, .error_icon:after { position: absolute; left: 44px; top: 20px; content: " "; height: 10px; width: 2px; background-color: #ffffff; } .error_icon:before { transform: rotate(45deg); } .error_icon:after { transform: rotate(-45deg); } .select2-results__options { padding-left: 0px; } .showcountry_code { width: 62px !important; } .textindent70 { text-indent: 70px; } .countrybox { width: 400px; height: 220px; overflow: auto; } .focusmanage { position: absolute; z-index: 10; width: 400px; height: 266px; box-sizing: border-box; padding: 10px; margin-top: -25px; background: #ffffff; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.13); border-radius: 2px; } .recoveryhead { margin-bottom: 10px; } .nopassword_container { display: none; background: #ebf4fb; border: 1px solid #cbe8ff; border-radius: 5px; width: 400px; height: auto; position: absolute; bottom: 50px; box-sizing: border-box; padding: 20px; } .nopassword_icon { display: table-cell; margin: 0px; height: 30px; width: 30px; color: #0091ff; font-size: 30px; vertical-align: middle; } .mailsearch { width: 400px; height: 30px; padding: 0px 0px; border: none; position: absolute; } .bolder1 { font-weight: bolder; } #ui-id-1 { height: 200px; width: auto; outline: none; border: none; overflow-x: hidden; overflow-y: scroll; } .ui-menu-item-wrapper { overflow: auto; } .nonclickelem { color: #626262; pointer-events: none; } .trustdevicebox { width: 500px; min-height: auto !important; } .trustdevice { width: auto; float: left; margin-right: 18px; min-width: 100px; margin-bottom: 0px; } .loadwithbtn { display: inline-block; height: 22px; width: 22px; border: 3px solid #fff; border-radius: 50%; position: absolute; margin: -2px; box-sizing: border-box; border-left: 3px solid transparent; animation-name: rotate1; animation-iteration-count: infinite; animation-duration: 1s; animation-timing-function: linear; } @keyframes rotate1 { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .waitbtn .waittext, .loadbtntext { display: inline-block; text-indent: 25px; } #waitbtn { display: none; } .loadwithbtn:before { display: inline-block; content: ""; height: 22px; width: 22px; border: 3px solid #ffffff30; border-radius: 50%; margin: -3px -11px; position: absolute; box-sizing: border-box; } .notnowbtn .loadwithbtn { border: 3px solid #3a3a3a; border-left: 3px solid transparent; } .trustdevice .loadwithbtn { display: none; } #recoverybtn, #problemsignin, .tryanother { display: none; bottom: 50px; width: fit-content; margin: 0px auto; cursor: pointer; } #recoverOption, #verify_totp_container, #verify_qr_container { display: none; } .backoption { width: 30px; height: 30px; font-size: 24px; display: inline-block; float: left; color: #666666; padding: 3px; box-sizing: border-box; margin-right: 5px; border-radius: 15px; cursor: pointer; } .backoption:hover { background: #f4f4f4; } .rec_head_text, .backoption { display: table-cell; } .options, .optionstry { display: table; width: 100%; height: auto; padding: 20px 50px; box-sizing: border-box; cursor: pointer; } .options:hover, .optionstry:hover { background-color: #f9f9f9; } .option_details, .option_details_try { height: auto; width: auto; padding-left: 10px; vertical-align: top; box-sizing: border-box; } .option_title_try { display: block; font-size: 16px; font-weight: 500; } .option_description { display: block; font-size: 13px; line-height: 20px; color: #696969; margin-top: 5px; } .img_option_try { margin: 12px 0px; } .img_option { font-size: 30px; color: #499aff; } .option_details { display: table-cell; } .problemsignincon, .recoverymodes, .addaptivetfalist, #recoverymodeContainer { width: auto; margin: 0px -50px; } .line { background-color: #f1f1f1; width: 100%; height: 2px; border-radius: 1px; margin-bottom: 20px; } .terminate_session_container .signin_head { margin-bottom: 20px; } #terminate_session_form .checkbox_div { position: relative; margin-bottom: 10px; } #terminate_session_form .checkbox_mod { margin-top: 30px; } #terminate_session_form .checkbox_check { left: 15px; } #terminate_session_form .checkbox_check:checked ~ .checkbox { background-color: #159aff; border-color: #159aff; } #terminate_session_form .showOneAuthLable { border: 1px solid #e7e7e7; border-radius: 10px 10px 0px 0px; box-sizing: border-box; max-width: 420px; height: auto; overflow: auto; margin-bottom: 0px; } .oneAuthLable { display: inline-flex; max-width: 420px; padding: 15px; align-items: center; margin-bottom: 10px; box-sizing: border-box; border: 1px solid #e7e7e7; border-top: none; border-radius: 0px 0px 15px 15px; } .oneauth_icon { display: inline-block; width: 40px; height: 40px; border-radius: 14px; background-size: 40px 40px; } .one_auth_icon_v2 { background-image: url(../images/oneAuth2.4b2a6945d5ecd70b703ba18f5f11eb68.png); } .oneAuthLable .text_container { flex: 1; margin: 0px 10px; } .oneAuthLable .text_header { font-size: 12px; font-weight: 500; margin-bottom: 4px; } .oneAuthLable .text_desc { font-size: 10px; line-height: 14px; } .oneAuthLable .togglebtn_div { height: 16px; padding: 4px; width: 30px; display: inline-block; position: relative; } .oneAuthLable .real_togglebtn { cursor: pointer; display: inline-block; position: relative; height: 16px; width: 30px; z-index: 1; opacity: 0; position: absolute; margin: 0px; } .real_togglebtn:checked ~ .togglebase { background-color: #10bc83; } .real_togglebtn:checked ~ .togglebase .toggle_circle { left: 16px; } .oneAuthLable .togglebase { height: 16px; width: 30px; display: inline-block; background: #ccc; border-radius: 10px; position: absolute; } .oneAuthLable .toggle_circle { transition: all 0.2s ease-in-out; height: 12px; width: 12px; background-color: #fff; border-radius: 10px; display: inline-block; position: absolute; left: 2px; top: 2px; box-shadow: 0px 0px 5px #999; } @media only screen and (-webkit-min-device-pixel-ratio: 2), not all, not all, not all, only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) { .google_icon { background-color: #fff; box-shadow: 0px 0px 2px #00000012, 0px 2px 2px #00000024; } .google_icon .fed_icon { background: url("../images/signin_icons@2x.c0c72f3600a6f5ec03c9119d5a7d0518.png") no-repeat transparent; background-size: 205px auto; background-position: -10px -10px; } .MS_icon { background-color: #2f2f2f; } .MS_icon .fed_icon { background: url("../images/signin_icons@2x.c0c72f3600a6f5ec03c9119d5a7d0518.png") no-repeat transparent; background-size: 205px auto; background-position: -38px -10px; } .linkedin_fed_box { background-color: #0966c2; } .linkedin_fed_box .fed_icon { background: url("../images/signin_icons@2x.c0c72f3600a6f5ec03c9119d5a7d0518.png") no-repeat transparent; background-size: 205px auto; background-position: -67px -11px; } .large_box .linked_fed_icon { background-position: -9px -174px; } .fb_fed_box { background-color: #1877f2; } .fb_fed_box .fed_icon { background: url("../images/signin_icons@2x.c0c72f3600a6f5ec03c9119d5a7d0518.png") no-repeat transparent; background-size: 205px auto; background-position: -94px -10px; } .zoho_icon { background: url(../images/signin_icons@2x.c0c72f3600a6f5ec03c9119d5a7d0518.png) no-repeat transparent; background-size: 205px auto; background-position: -152px -174px; } } @media only screen and (max-width: 600px) { body { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); } .nopassword_container { width: calc(100% - 60px); padding: 10px; box-sizing: border-box; } .signin_container { width: 100%; box-shadow: none; margin: 0 auto; position: relative; z-index: 1; margin-top: 40px; height: 27rem; overflow: hidden; } #captcha_img, #captcha, #bcaptcha_img, #bcaptcha { border: 2px solid #f4f6f8; border-radius: 2px; text-indent: 3px; width: 50%; outline: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; } .textbox, #verify_totp { background-color: transparent; border: none; border-bottom: 2px solid #f4f6f8; text-indent: 0px; border-radius: 0px; } .textbox:focus, #verify_totp { border: none; border-bottom: 2px solid #159aff; } .errorborder:focus, .textbox.errorborder { border-bottom: 2px solid #ff8484; } .backoption { margin-bottom: 10px; } .img_option { margin: 0px 10px 0px 0px !important; } .bg_one, .line { display: none; } .signin_head { margin-bottom: 30px; } .btn { margin-top: 10px; border-radius: 4px; } .changeloadbtn { border-radius: 22px; } .more { margin-right: 0px !important; } .textindent42 { text-indent: 36px; } .textindent62 { text-indent: 56px; } .textintent52 { text-indent: 46px; } .select2-selection { background-color: #f1f9ff; } .devices .select2-selection { background-color: #f1f9ff; } .select2-search__field { width: 280px; } .applynormal { width: 46% !important; min-width: 0px; } .borderless, .borderlesstry { line-height: 12px; font-size: 14px; } .alert_message, .error_message { max-width: 300px; width: 75%; } .Alert, .Errormsg { max-width: 400px !important; padding: 0px; min-width: 300px; } .error_icon:before, .error_icon:after { left: 24px; } ::placeholder { font-weight: 500; } .mobilehide { display: none !important; } #forgotpassword { margin-bottom: 0px; } #forgotpassword a { display: inline-block; } .fielderror { position: absolute; margin-top: 5px; } .trustdevicebox { width: auto; height: auto; } #recoverybtn, #problemsignin, .tryanother { position: initial !important; margin-top: 70px !important; } .problemsignincon, .recoverymodes, #recoverymodeContainer { width: 100%; margin-left: 0px; } } .hover-tool-tip { position: absolute; top: 15px; left: 335px; background: #fff; padding: 20px; box-shadow: 0px 0px 10px #0000001a; transition: opacity 0.1s; border-radius: 5px; z-index: 9; opacity: 0; } .hover-tool-tip::after { content: ""; position: absolute; width: 0; height: 0; margin-left: -8px; top: 25px; left: 0; box-sizing: border-box; border: 6px solid black; border-color: #ffffff transparent transparent #ffffff; transform-origin: 0 0; transform: rotate(-45deg); box-shadow: -6px -6px 10px 0 #0000001a; } .hover-tool-tip.no-arrow::after { content: none; } .hover-tool-tip ul { padding: 0; margin: 0px 0px 0px 15px; list-style: none; } .hover-tool-tip p { margin: 0px 0px 10px 0px; font-size: 14px; font-weight: 500; } .hover-tool-tip ul li { font-size: 12px; display: flex; align-items: center; white-space: nowrap; line-height: 25px; } .hover-tool-tip ul li.success::before { background-color: #0dbc83; } .hover-tool-tip ul li::before { content: ""; margin-right: 10px; background-color: red; width: 8px; height: 8px; border-radius: 50%; } .titlename, .service_name { display: none !important; } .signin_head { padding-top: 20px; } #login_id { text-indent: 0 !important; } .showcountry_code { display: none !important; } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-css line-numbers" header="fpwd.css" footer="button" scroll="set-scroll" %}}@font-face { font-family: "Roboto Mono"; font-weight: 400; font-style: normal; src: url("https://webfonts.zohowebstatic.com/robotomonoregular/font.eot"); src: url("https://webfonts.zohowebstatic.com/robotomonoregular/font.eot?#iefix") format("eot"), url("https://webfonts.zohowebstatic.com/robotomonoregular/font.woff2") format("woff2"), url("https://webfonts.zohowebstatic.com/robotomonoregular/font.woff") format("woff"), url("https://webfonts.zohowebstatic.com/robotomonoregular/font.ttf") format("truetype"), url("https://webfonts.zohowebstatic.com/robotomonoregular/font.svg#RobotoMono-Regular") format("svg"); } @font-face { font-family: "LatoLgt"; font-weight: 300; font-style: normal; src: url("https://webfonts.zohowebstatic.com/latolight/font.eot"); src: url("https://webfonts.zohowebstatic.com/latolight/font.eot?#iefix") format("eot"), url("https://webfonts.zohowebstatic.com/latolight/font.woff2") format("woff2"), url("https://webfonts.zohowebstatic.com/latolight/font.woff") format("woff"), url("https://webfonts.zohowebstatic.com/latolight/font.ttf") format("truetype"), url("https://webfonts.zohowebstatic.com/latolight/font.svg#Lato-Light") format("svg"); } @font-face { font-family: "LatoReg"; font-weight: 400; font-style: normal; src: url("https://webfonts.zohowebstatic.com/latoregular/font.eot"); src: url("https://webfonts.zohowebstatic.com/latoregular/font.eot?#iefix") format("eot"), url("https://webfonts.zohowebstatic.com/latoregular/font.woff2") format("woff2"), url("https://webfonts.zohowebstatic.com/latoregular/font.woff") format("woff"), url("https://webfonts.zohowebstatic.com/latoregular/font.ttf") format("truetype"), url("https://webfonts.zohowebstatic.com/latoregular/font.svg#Lato-Regular") format("svg"); } @font-face { font-family: "LatoSB"; font-weight: 700; font-style: normal; src: url("https://webfonts.zohowebstatic.com/latobold/font.eot"); src: url("https://webfonts.zohowebstatic.com/latobold/font.eot?#iefix") format("eot"), url("https://webfonts.zohowebstatic.com/latobold/font.woff2") format("woff2"), url("https://webfonts.zohowebstatic.com/latobold/font.woff") format("woff"), url("https://webfonts.zohowebstatic.com/latobold/font.ttf") format("truetype"), url("https://webfonts.zohowebstatic.com/latobold/font.svg#Lato-Bold") format("svg"); } @font-face { font-family: "LatoB"; font-weight: 900; font-style: normal; src: url("https://webfonts.zohowebstatic.com/latoblack/font.eot"); src: url("https://webfonts.zohowebstatic.com/latoblack/font.eot?#iefix") format("eot"), url("https://webfonts.zohowebstatic.com/latoblack/font.woff2") format("woff2"), url("https://webfonts.zohowebstatic.com/latoblack/font.woff") format("woff"), url("https://webfonts.zohowebstatic.com/latoblack/font.ttf") format("truetype"), url("https://webfonts.zohowebstatic.com/latoblack/font.svg#Lato-Black") format("svg"); } @font-face { font-family: "Zoho_Puvi_Regular"; src: url("https://static.zohocdn.com/zohofonts/zohopuvi/4.0/Zoho_Puvi_Regular.eot"); src: url("https://static.zohocdn.com/zohofonts/zohopuvi/4.0/Zoho_Puvi_Regular.eot") format("embedded-opentype"), url("https://static.zohocdn.com/zohofonts/zohopuvi/4.0/Zoho_Puvi_Regular.woff2") format("woff2"), url("https://static.zohocdn.com/zohofonts/zohopuvi/4.0/Zoho_Puvi_Regular.otf") format("opentype"); font-weight: normal; font-style: normal; /* font-display: swap */ } @font-face { font-family: "signinicon"; src: url(/images/signinicon.eot); src: url(/images/signinicon.eot) format("embedded-opentype"), url(/images/signinicon.woff2) format("woff2"), url(/images/signinicon.ttf) format("truetype"), url(/images/signinicon.woff) format("woff"), url("../images/fonts/signinicon.651f82b8f8f51f33cd0b6331f95b5b4f.svg") format("svg"); font-weight: normal; font-style: normal; font-display: block; } [class^="icon-"], [class*=" icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ font-family: "signinicon" !important; speak: never; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-device:before { content: "\e90a"; } .icon-reload:before { content: "\e912"; } .icon-backarrow:before { content: "\e900"; } .icon-email:before { content: "\e904"; } .icon-hide:before { content: "\e907"; } .icon-otp:before { content: "\e90c"; } .icon-show:before { content: "\e914"; } body { margin: 0px; } ::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ color: #0003; } .bg_one { display: block; position: fixed; top: 0px; left: 0px; height: 100%; width: 100%; /* background: url("../images/bg.49756b7c711696d95133fa95451f8e13.svg") transparent; */ background-size: auto 100%; z-index: -1; } .titlename { display: block; font-size: 24px; color: #000; font-weight: 400; text-transform: capitalize; width: auto; margin-bottom: 20px; } .Alert, .Errormsg { display: flex; align-items: center; justify-content: center; margin: auto; height: auto; min-width: 200px; width: fit-content; width: -moz-fit-content; max-width: 600px; background-color: #032239; border-radius: 6px; position: fixed; top: -100px; left: 0px; right: 0px; transition: all 0.2s ease; padding: 0px 20px; z-index: 2; } .tick_icon, .error_icon { display: inline-block; height: 20px; width: 20px; background-color: #22c856; border-radius: 50%; background-size: 60px; margin: 0px 15px; float: left; position: relative; } .tick_icon:after { display: block; content: ""; height: 5px; width: 9px; border-bottom: 2px solid #fff; border-left: 2px solid #fff; transform: rotate(-45deg); margin: 7px 6px; box-sizing: border-box; } .alert_message, .error_message { display: inline-block; font-size: 14px; color: #fff; line-height: 18px; margin: 16px 0px; margin-right: 20px; max-width: 510px; } .error_icon { background-color: #ff676f; } .error_icon:before, .error_icon:after { position: absolute; left: 9px; top: 5px; content: " "; height: 10px; width: 2px; background-color: #ffffff; } .error_icon:before { transform: rotate(45deg); } .error_icon:after { transform: rotate(-45deg); } .loader { width: 36px; height: 36px; border: 4px solid #159aff; border-bottom: 4px solid transparent; border-radius: 50%; display: block; box-sizing: border-box; animation: rotate 1s linear infinite; position: absolute; top: 0px; z-index: 7; bottom: 0px; left: 0px; right: 0px; margin: auto; } @keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .blur { top: 0px; left: 0px; bottom: 0px; background: #fff; width: 100%; height: auto; margin: auto; position: absolute; z-index: 3; opacity: 0.9; display: block; } .bold_font { font-weight: 500; } .searchparent { height: auto; } .textbox_div { display: block; margin-bottom: 30px; position: relative; } .textbox { display: block; width: 100%; height: 44px; box-sizing: border-box; border-radius: 2px; text-indent: 12px; font-size: 16px; outline: none; border: none; padding-right: 12px; transition: all 0.2s ease-in-out; background: #f9f9f9; border: 1px solid #dddddd; } .textbox:hover, .textbox:valid, .textbox:focus { border: 1px solid #b9b9b9; } #last_password { padding-right: 40px; } ::-webkit-credentials-auto-fill-button { visibility: hidden; pointer-events: none; position: absolute; right: 0px; } input[type="text"], input[type="password"], input[type="email"], input[type="number"] { -webkit-appearance: none; -moz-appearance: none; appearance: none; } .errorborder { border: 2px solid #ff8484 !important; } .textbox:-webkit-autofill, .textbox:-webkit-autofill:hover, .textbox:-webkit-autofill:focus, .textbox:-webkit-autofill:active { -webkit-box-shadow: inset 0 0 0px 9999px #f9f9f9; -webkit-text-fill-color: black; background-color: #f9f9f9 !important; } .fielderror { display: none; font-size: 14px; margin-top: 10px; } .fielderror a { text-decoration: none; color: #309ff4; } #captcha_container { display: none; border: 1px solid #dddddd; width: 250px; padding: 10px; box-sizing: border-box; margin-top: 10px; border-radius: 4px; } #captcha { width: 100%; display: inline-block; height: 40px; padding: 0px 12px; text-indent: 0px; background: #f9f9f9; } #captcha_img { background-color: #ffffff; border: none; height: 60px; width: 150px; margin-left: 20px; } #captcha_img img { height: 50px; width: 150px; box-sizing: border-box; margin: 0px; } .reloadcaptcha { height: 30px; width: 30px; border-radius: 50%; display: inline-block; cursor: pointer; position: absolute; right: 20px; top: 20px; background-color: #f2f2f2; font-size: 30px; box-sizing: border-box; color: #0006; } .reloadcaptcha:hover { color: #000000b3; } .load_captcha_btn { animation: spin 0.5s linear infinite; } #Last_password_div .head_info { margin: 0px; } .zeroheight { width: 0px; height: 0px; overflow: hidden; display: block; } .btn { cursor: pointer; display: block; width: 100%; height: 44px; border-radius: 4px; letter-spacing: 0.5; font-size: 14px; font-weight: 600; outline: none; border: none; margin: auto; text-transform: uppercase; margin-bottom: 30px; transition: width 0.2s ease-in-out; } .blue { background-color: #159aff; color: #fff; } .grey { background-color: #f3f3f3; color: #3a3a3a; letter-spacing: 0px; } .green { box-shadow: 0px 2px 2px #fff; background-color: #00c573; color: #fff; } .blue:hover { background: #118be9; } .changeloadbtn { display: block; height: 44px; width: 44px; border-radius: 22px; padding: 0px; } .changeloadbtn:before { content: ""; height: 20px; width: 20px; display: inline-block; margin: 9px; border: 3px solid #fff; border-top: 3px solid #5ab7fe; border-radius: 50%; animation: spin 0.5s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .doneBtn { position: relative; } .doneBtn:before { content: ""; opacity: 1; height: 15px; width: 6px; border: unset; border-radius: unset; transform-origin: left top; border-right: 3px solid #fff; border-top: 3px solid #fff; left: 12px; top: 23px; margin: 0px; position: absolute; transform: scaleX(-1) rotate(135deg); animation: tickmark 0.8s ease; } @keyframes tickmark { 0% { height: 0px; width: 0px; border-radius: unset; opacity: 1; } 20% { height: 0px; width: 6px; opacity: 1; border-radius: unset; } 40% { height: 15px; width: 6px; opacity: 1; border-radius: unset; } 100% { height: 15px; width: 6px; opacity: 1; border-radius: unset; } } .doneWithContent span:before { content: ""; opacity: 1; height: 14px; width: 5px; border: unset; border-radius: unset; transform-origin: left top; border-right: 3px solid #fff; border-top: 3px solid #fff; display: inline-block; margin: 0px; position: relative; transform: scaleX(-1) rotate(135deg); top: 12px; left: -21px; } .doneWithContent { padding-left: 21px; position: relative; } .doneWithContent:after { position: absolute; height: 100%; content: ""; display: block; left: 0px; top: 0px; background: #00000008; animation-name: btn_loading; animation-duration: 4.3s; animation-iteration-count: 1; animation-timing-function: linear; } .restrict_icon { display: block; height: 102px; width: 100%; background: url(../images/URLError.76c2ce33de912ad7ae9d2fbfd93ec2c1.png) no-repeat transparent; background-size: auto 100%; margin: auto; margin-top: 30px; background-position: center; } .error_portion { display: none; } .error_content { text-align: center; } .error_content .error_header { font-size: 20px; font-weight: 500; margin-top: 30px; line-height: 26px; } .error_content .error_desc { font-size: 16px; margin: 10px 0px 30px 0px; line-height: 24px; font-weight: 500; color: #000000cc; } .retry_button { line-height: 44px; } .doneWithContent:hover { background: #159aff; } .doneWithContent span { width: 100%; display: block; transition: width 0.2s ease-in-out; white-space: nowrap; } @keyframes btn_loading { 0% { width: 0%; } 100% { width: 100%; } } .resend_label { display: block; text-align: center; } .push_resend { font-size: 14px; color: #626262; font-weight: 500; line-height: 16px; cursor: none; } .bottom_option { cursor: pointer; width: max-content; font-size: 16px; font-weight: 500; text-decoration: none; color: #0091ff; } .bottom_line_opt { width: 100%; display: flex; justify-content: center; position: absolute; left: 0px; bottom: 40px; } .errorlabel { color: #e92b2b; } .show_hide_password { font-size: 24px; color: #00000066; position: relative; top: -34px; right: 13px; float: right; cursor: pointer; line-height: 23px; } .show_hide_password:hover { color: #000000b3; } .select2-results__option { list-style-type: none; height: 40px; box-sizing: border-box; padding: 12px 20px; font-size: 14px; line-height: 16px; } .select2-search__field { width: 380px; height: 32px; border: none; outline: none; background: #f7f7f7; border-radius: 2px; margin: 10px 8px; font-size: 14px; padding: 10.5px 8px; } .select2-selection { display: inline-block; outline: none; background-color: #f9f9f9; height: 40px; text-align: center; padding: 10px; box-sizing: border-box; cursor: pointer; } .selection { transition: all 0.2s ease-in-out; -webkit-user-select: none; display: inline-block; white-space: nowrap; overflow: hidden; width: 0px; height: 43px; } #select2-combobox-results { padding-left: 0px; max-height: 200px; overflow-y: scroll; overflow-x: hidden; width: 400px; margin-top: 10px; margin-bottom: 0px; background: white; } .select2-container--open { z-index: 10; background: #ffffff; box-shadow: 0px 5px 8px 2px #0000000d; width: auto; box-sizing: border-box; } .select2 { position: absolute; background: transparent; box-shadow: none; display: none; margin: 2px; } .select2-results__options { overflow-y: auto; max-height: 200px; } .selection { width: auto; width: -moz-fit-content; width: -webkit-fit-content; margin: auto; display: block; } .select2-search--hide, .select2-selection__clear { display: none; } #country_code_select { width: 50px; height: 40px; position: absolute; opacity: 0; display: none; z-index: 0; } .select_country_code { width: 50px; height: 40px; display: inline-block; float: left; position: absolute; line-height: 39px; text-align: center; font-size: 16px; color: black; display: none; z-index: 0; } .pic { width: 20px; height: 14px; background-size: 280px 252px; background-image: url("../images/Flags2x.0b8394efb0ea9167cef2465fb8f63d78.png"); float: left; } .cc { float: right; color: #aeaeae; } .cn { margin-left: 10px; } .select2-results__option--highlighted { background: #f4f6f8; } .searchparent { height: auto; } .select2-results__options { padding-left: 0px; } .optionstry { display: table; width: 100%; height: auto; padding: 20px 50px; box-sizing: border-box; cursor: pointer; } .optionstry:hover { background-color: #f9f9f9; } .img_option_try { margin: 12px 0px; } .img_option { display: table-cell; width: 30px; color: #499aff; font-size: 30px; vertical-align: top; height: 30px; } .option_details_try { height: auto; width: auto; padding-left: 12px; vertical-align: top; box-sizing: border-box; } .option_title_try { display: block; font-size: 16px; font-weight: 500; } .option_description { display: block; font-size: 13px; line-height: 20px; color: #000c; margin-top: 5px; } #email_confirm_div .optionstry, #mobile_confirm_div .optionstry { padding: 22px 40px; } #email_confirm_div .img_option, #mobile_confirm_div .img_option { font-size: 20px; width: 20px; height: 20px; } #email_confirm_div .option_details_try, #mobile_confirm_div .option_details_try { line-height: 20px; } .otp_container::after, .mini_txtbox:after { content: attr(placeholder); height: 54px; line-height: 54px; position: absolute; color: #b9bcbe; left: 15px; top: 0px; z-index: 1; } .mini_txtbox:after { line-height: 42px; height: 42px; } .hidePlaceHolder::after { z-index: -1 !important; } .otp_verify { margin-top: 10px; } .toggle_active { background: #f9f9f9; border-radius: 2px; } .optionmod { margin-left: 0px; box-sizing: border-box; } .option_title_try { color: #000000; } .mini_btn { margin-left: 10px; width: 30%; float: right; font-size: 12px; margin-bottom: 0px; margin-right: 0px; } .backoption { width: 30px; height: 30px; font-size: 21px; display: inline-block; float: left; color: #666666; padding: 4px 0px; box-sizing: border-box; margin-right: 5px; border-radius: 15px; cursor: pointer; font-weight: 500; text-align: center; } .backoption:hover { background: #f4f4f4; } #select_reocvery_mobile .fieldcontainer, #other_options_div .fieldcontainer, #select_reocvery_email .fieldcontainer { width: auto; margin: 0 -40px; margin-bottom: 40px; } .text16 { display: block; text-align: center; margin-bottom: 30px; color: #626262; font-size: 16px; font-weight: 500; text-decoration: none; } .pointer { cursor: pointer; } .pass_policy { color: #8c8c8c; font-size: 14px; padding-top: 10px; } .nomargin { display: block; margin: 0px; } .tryanother, .extra_options { width: fit-content; margin: 0 auto; cursor: pointer; margin-top: 20px; } .bluetext_action { display: inline-block; font-size: 14px; color: #159aff; font-weight: 500; line-height: 16px; cursor: pointer; margin-top: 5px; } .nonclickelem { color: #626262; pointer-events: none; cursor: none; } .bluetext_action a { text-decoration: none; } .recovery_container { display: block; width: 480px; height: 350px; background-color: #fff; box-shadow: 0px 2px 30px 0px #2b2b2b17; margin: auto; position: relative; z-index: 1; overflow: hidden; } .recovery_box { width: 480px; min-height: 500px; height: auto; background: #fff; box-sizing: border-box; padding: 40px 40px; border-radius: 2px; transition: all 0.1s ease-in-out; float: left; overflow-y: auto; display: table-cell; border-right: 2px solid #f1f1f1; } .menuicon { display: inline-block; float: left; height: 14px; width: 14px; padding: 14px; font-size: 14px; text-align: center; } .user_info { display: inline-flex; justify-content: space-between; border: 1px solid #eeeeee; margin-bottom: 20px; border-radius: 7px; cursor: pointer; box-sizing: border-box; max-width: 100%; flex-wrap: nowrap; } .user_info_space { margin-top: 20px; } .support_temp_info { margin-bottom: 30px; line-height: 24px; } .menutext { display: inline-block; font-size: 16px; padding: 12px 14px; line-height: 20px; width: auto; word-break: break-all; } .fieldcontainer { margin-bottom: 40px; } .change_user { position: relative; float: right; font-size: 14px; padding: 0px 14px 0px 0px; display: inline-block; color: #0091ff; line-height: 38px; font-weight: 500; display: flex; align-items: center; justify-content: center; } .info_head { display: block; font-size: 20px; font-weight: 500; margin-bottom: 20px; line-height: 30px; overflow: auto; transition: all 0.1s ease-in-out; } .info_head #headtitle { display: block; } .head_info { font-size: 16px; margin: 10px 0px 0px 0px; line-height: 24px; font-weight: 400; color: #000000e6; } .user_info_space + .head_info { margin: 0px; } .otp_container { display: block; width: 100%; height: 54px; box-sizing: border-box; border-radius: 2px; font-size: 16px; outline: none; padding: 0px 15px; transition: all 0.2s ease-in-out; background: #f9f9f9; border: 1px solid #dddddd; text-indent: 0px; } .customOtp { border: none; outline: none; background: transparent; height: 100%; font-size: 16px; text-align: left; width: 22px; padding: 0px; } .textindent42 { text-indent: 46px; } .textindent62 { text-indent: 56px; } .textintent52 { text-indent: 51px; } .box_header_load { display: block; height: auto; padding: 50px 0px; -webkit-animation-name: blink; /* Safari 4.0 - 8.0 / -webkit-animation-duration: 1s; / Safari 4.0 - 8.0 */ animation-name: blink; animation-duration: 1s; animation-iteration-count: infinite; animation-timing-function: linear; opacity: 0.05; } .box_head_load { display: block; width: 180px; height: 18px; border-radius: 8px; background-color: #000; } .blink_btn { -webkit-animation-name: blink; /* Safari 4.0 - 8.0 / -webkit-animation-duration: 1s; / Safari 4.0 - 8.0 */ animation-name: blink; animation-duration: 1s; animation-iteration-count: infinite; animation-timing-function: linear; opacity: 0.05; } .box_define_load { display: block; max-width: 800px; width: 100%; height: 16px; border-radius: 8px; background-color: #000; margin-top: 20px; } @keyframes blink { 0% { opacity: 0.08; } 50% { opacity: 0.01; } 100% { opacity: 0.08; } } #loading_div, #Last_password_div, #mobile_confirm_div, #email_confirm_div, #confirm_otp_div, #other_options_div, #contact_support_div, #change_password_div, #recovery_device_div, #password_matched_div, #username_div, #terminate_session_div { display: none; } #multiple_reocvery_mobile, #single_reocvery_mobile, #multiple_reocvery_email, #single_reocvery_email { display: none; } .verify_mob_container, .verify_email_container, .select_device_othermodes { display: none; } .devices { position: relative; padding: 0px; width: fit-content !important; width: -moz-fit-content !important; width: -webkit-fit-content !important; margin: auto; border-radius: 8px; box-sizing: border-box; margin-bottom: 30px; background: #fcfcfc; } .device_title { position: absolute; top: 12px; left: 48px; font-size: 10px; font-weight: 600; text-transform: uppercase; color: #000000b3; line-height: 14px; z-index: 11; } .select2-container--device_select { margin: 0px; display: block; position: relative; margin: auto; cursor: pointer; border: 1px solid #dddddd; border-radius: 8px; overflow: hidden; } #recovery_device_select + .select2-container { width: fit-content !important; width: -moz-fit-content !important; width: -webkit-fit-content !important; } .select2-container--device_select .select2-selection__arrow { position: absolute; top: 0px; right: 14px; width: auto; height: 100%; } .select2-container--device_select .select2-selection__arrow b { border-color: transparent #00000066 #00000066 transparent; border-style: solid; transform: rotate(45deg); border-width: 2px; height: 4px; width: 4px; position: relative; border-radius: 1px; display: inline-block; top: 24px; } .select2-container--device_select .selection { margin: auto; height: 60px; box-sizing: border-box; min-width: 140px; } .select2-container--device_select .select2-selection { height: unset; padding: 12px 34px 12px 14px; } .select2-container--device_select .select_con { font-weight: 400; margin-left: 12px; margin-top: 16px; } .select2-container--device_select .select_icon { margin-top: 7px; } .select2-results__options { margin: 0px; } .select2-results__options .select_con { margin: 0px; } .select2-dropdown .select_con { width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .hideArrow .select2-selection { cursor: default; padding: 12px 14px; } .hideArrow .select2-selection__arrow { display: none; } .secondary_devices { display: none; width: 150px; margin: auto; padding: 8px 18px; border-radius: 18px; background-color: #f4f6f8; border: none; overflow: hidden; max-width: 200px; text-overflow: ellipsis; font-size: 16px; outline: none; } .secondary_devices .select2-container--open { width: 200px; } .device_select { display: block; position: unset; width: auto !important; } .device_selection { padding: 8px 14px; border-radius: 18px; } .deviceparent { text-align: center; display: none; text-overflow: ellipsis; overflow: hidden; width: 100%; white-space: nowrap; } .deviceparent { display: block; } .deviceinfo { display: inline-block; } .devicetext { margin: 0 auto; margin-left: 30px; } .select_icon { float: left; font-size: 20; color: #499aff; } .select_con { float: left; width: auto; overflow: hidden; margin-left: 5px; text-overflow: ellipsis; } .options_selct { max-width: 180px; font-size: 14px; line-height: 20px; font-weight: 500; width: auto; } .contact_support_class { min-height: unset; } .contact_support_class .recovery_box { min-height: unset; } #mfa_totp_section { display: none; } /* Terminate_session page */ #terminate_session_form { overflow: auto; } #terminate_session_form .checkbox_div { position: relative; } #terminate_session_form .checkbox_check { position: absolute; z-index: 1; opacity: 0; left: 15px; margin: 0px; height: 16px; cursor: pointer; width: 16px; } #terminate_session_form .checkbox { float: left; z-index: 0; height: 12px; width: 12px; display: inline-block; border: 2px solid #ccc; border-radius: 2px; position: relative; top: 1.5px; overflow: hidden; background-color: #fff; } #terminate_session_form .checkbox_check:hover ~ .checkbox { border: 2px solid #159aff; } #terminate_session_form .checkbox_check:checked ~ .checkbox { background-color: #159aff; border-color: #159aff; } #terminate_session_form .checkbox .checkbox_tick { display: block; height: 3px; width: 8px; border-bottom: 2px solid #fff; border-left: 2px solid #fff; transform: rotate(-45deg); margin: 2px 1px; } #terminate_session_form .checkbox_label { cursor: pointer; margin: 0px 6px; line-height: 20px; width: 85%; display: inline-block; } #terminate_session_form .showOneAuthLable { border: 1px solid #e7e7e7; border-radius: 10px 10px 0px 0px; box-sizing: border-box; max-width: 420px; } .oneAuthLable { display: inline-flex; max-width: 420px; padding: 15px; align-items: center; box-sizing: border-box; border: 1px solid #e7e7e7; border-top: none; border-radius: 0px 0px 15px 15px; } .oneauth_icon { display: inline-block; width: 40px; height: 40px; border-radius: 14px; background-size: 40px 40px; } .one_auth_icon_v2 { background-image: url(../images/oneAuth2.4b2a6945d5ecd70b703ba18f5f11eb68.png); } .oneAuthLable .text_container { flex: 1; margin: 0px 10px; } .oneAuthLable .text_header { font-size: 12px; font-weight: 500; margin-bottom: 4px; } .oneAuthLable .text_desc { font-size: 10px; line-height: 14px; } .oneAuthLable .togglebtn_div { height: 16px; padding: 4px; width: 30px; display: inline-block; position: relative; } .oneAuthLable .real_togglebtn { cursor: pointer; display: inline-block; position: relative; height: 16px; width: 30px; z-index: 1; opacity: 0; position: absolute; margin: 0px; } .real_togglebtn:checked ~ .togglebase { background-color: #10bc83; } .real_togglebtn:checked ~ .togglebase .toggle_circle { left: 16px; } .oneAuthLable .togglebase { height: 16px; width: 30px; display: inline-block; background: #ccc; border-radius: 10px; position: absolute; } .oneAuthLable .toggle_circle { transition: all 0.2s ease-in-out; height: 12px; width: 12px; background-color: #fff; border-radius: 10px; display: inline-block; position: absolute; left: 2px; top: 2px; box-shadow: 0px 0px 5px #999; } #terminate_session_submit { margin-top: 30px; } @media only screen and (max-width: 600px) { body { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); } .bg_one { display: none; } .alert_message, .error_message { max-width: 300px; width: 75%; } .Alert, .Errormsg { max-width: 400px !important; padding: 0px; min-width: 300px; } .textbox, .otp_container { background-color: transparent; border: none; border-bottom: 2px solid #f4f6f8; border-radius: 0px; } .textbox { transition: unset; } .textbox:-webkit-autofill, .textbox:-webkit-autofill:hover, .textbox:-webkit-autofill:focus, .textbox:-webkit-autofill:active { -webkit-box-shadow: inset 0 0 0px 9999px white; -webkit-text-fill-color: black; } .textbox:valid, .textbox:hover { border: none; border-bottom: 2px solid #159aff; } #captcha { border-radius: 2px; outline: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; } .changeloadbtn { border-radius: 22px; } .btn { margin-top: 10px; border-radius: 4px; } .mini_btn { margin-top: 0px; } .optionstry:hover { background-color: #fff; } .img_option { margin: 0px 10px 0px 0px !important; } .option_details_try { color: #555555; display: inline-block; margin: 0px; } #multiple_reocvery_mobile .fieldcontainer, #multiple_reocvery_email .fieldcontainer, #other_options_div .fieldcontainer { margin: 0 -30px; margin-bottom: 40px; } .optionmod { margin-left: 0px; padding: 15px 30px; line-height: 24px; } .recovery_box { width: 100%; padding: 0px 30px; height: auto; border-right: none; padding-bottom: 40px; } .recovery_container { width: 100%; box-shadow: none; margin: 0 auto; position: relative; z-index: 1; margin-top: 40px; height: auto; overflow: hidden; } .info_head { margin-bottom: 30px; } } .devices .select2-container--open { background: transparent; } @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) { .restrict_icon { background: url(../images/URLError2x.5e762e74a83ee0cda60b8f7c9df299a8.png) no-repeat transparent center/auto 100%; } } .hide { display: none; } .hover-tool-tip { position: absolute; top: 15px; left: 335px; background: #fff; padding: 20px; box-shadow: 0px 0px 10px #0000001a; transition: opacity 0.1s; border-radius: 5px; z-index: 9; opacity: 0; } .hover-tool-tip::after { content: ""; position: absolute; width: 0; height: 0; margin-left: -8px; top: 25px; left: 0; box-sizing: border-box; border: 6px solid black; border-color: #ffffff transparent transparent #ffffff; transform-origin: 0 0; transform: rotate(-45deg); box-shadow: -6px -6px 10px 0 #0000001a; } .hover-tool-tip.no-arrow::after { content: none; } .hover-tool-tip ul { padding: 0; margin: 0px 0px 0px 15px; list-style: none; } .hover-tool-tip p { margin: 0px 0px 10px 0px; font-size: 14px; font-weight: 500; } .hover-tool-tip ul li { font-size: 12px; display: flex; align-items: center; white-space: nowrap; line-height: 25px; } .hover-tool-tip ul li.success::before { background-color: #0dbc83; } .hover-tool-tip ul li::before { margin-right: 10px; background-color: red; width: 8px; height: 8px; border-radius: 50%; } .titlename { display: none !important; } /* #lookup_div { display: none !important; } */ #lookup_div .head_info { font-size: 0; } #lookup_div .head_info::before { content: "Enter your email address"; font-size: 15px; } .info_head { padding-top: 20px; } .rec_modes_other_options { display: none !important; } .recovery_container { height: 29rem; } .Last_password_div { margin-top: -30px; } {{%/panel_with_adjustment%}} Copy the code given below and paste it in the respective files located in the client directory ({{%badge%}}birthdaygreetings/src/components/{{%/badge%}}) using an IDE and save the file. {{%panel_with_adjustment class="language-css line-numbers" header="form.css" footer="button" scroll="set-scroll" %}}.form-container { display: grid; height: 190px; grid-template-columns: 1fr 1fr; grid-template-rows: auto auto; gap: 20px; padding: 20px; max-width: 786px; margin: 75px auto; box-shadow: 0 4px 8px rgb(0 0 0 / 10%); border-radius: 8px; background-color: #fff; } .form-group { width: 100%; height: 0px; display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-top: 42px; margin-bottom: 57px; } label { font-size: 19px; font-weight: bold; width: 123px; } input { font-size: 13px; padding: 8px; border: 1px solid #ccc; border-radius: 4px; flex: 1; } input:focus { border-color: #007bff; outline: none; box-shadow: 0 0 4px rgba(0, 123, 255, 0.25); } .form-container > .form-group:nth-child(1) { grid-column: 1; grid-row: 1; } .form-container > .form-group:nth-child(2) { grid-column: 2; grid-row: 1; } .form-container > .form-group:nth-child(3) { grid-column: 1; grid-row: 2; } .form-container > .form-group:nth-child(4) { grid-column: 2; grid-row: 2; } .submit-form{ height: 50px; width: 100%; margin-bottom: 64px; } .submit-btn{ background-color: #58d358; border: none; border-radius: 10px; color: #fff; font-size: 20px; height: 46px; margin-left: 42%; width: 16%; } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="form.jsx" footer="button" scroll="set-scroll" %}}import { useState } from "react"; import "./form.css"; function Form({ onSubmit }) { const [formData, setFormData] = useState({ name: "", birthday: "", message: "", email: "", }); const handleChange = (e) =&gt; { const { id, value } = e.target; setFormData({ ...formData, [id]: value }); }; const handleSubmit = (e) =&gt; { e.preventDefault(); onSubmit(formData); }; const today = new Date().toISOString().split("T")[0]; return ( &lt;&gt; &lt;form onSubmit={handleSubmit}&gt; &lt;div className="form-container"&gt; &lt;div className="form-group"&gt; &lt;label htmlFor="name"&gt;Name&lt;/label&gt; &lt;input type="text" id="name" value={formData.name} onChange={handleChange} /&gt; &lt;/div&gt; &lt;div className="form-group"&gt; &lt;label htmlFor="birthday"&gt;Birthday&lt;/label&gt; &lt;input type="date" id="birthday" value={formData.birthday} onChange={handleChange} max={today} /&gt; &lt;/div&gt; &lt;div className="form-group"&gt; &lt;label htmlFor="message"&gt;Message&lt;/label&gt; &lt;input type="text" id="message" value={formData.message} onChange={handleChange} /&gt; &lt;/div&gt; &lt;div className="form-group"&gt; &lt;label htmlFor="email"&gt;Email&lt;/label&gt; &lt;input type="email" id="email" value={formData.email} onChange={handleChange} /&gt; &lt;/div&gt; &lt;/div&gt; &lt;div className="submit-form"&gt; &lt;button className="submit-btn" type="submit"&gt; Submit &lt;/button&gt; &lt;/div&gt; &lt;/form&gt; &lt;/&gt; ); } export default Form; {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-css line-numbers" header="modal.css" footer="button" scroll="set-scroll" %}}.modal { display: flex; justify-content: center; align-items: center; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 1000; padding: 20px; box-sizing: border-box; } .modal-content { background-color: #fff; padding: 20px; border-radius: 8px; width: 100%; max-width: 500px; position: relative; box-shadow: 0px 2px 15px rgba(0, 0, 0, 0.1); } .edit-heade{ width: 100%; height: 60px; display: flex; } .modal-header h2{ padding-left: 185px; font-size: 1.5rem; margin-bottom: 20px; } .modal-close { position: absolute; top: 10px; right: 15px; font-size: 1.5rem; cursor: pointer; color: #333; } .modal-input-group { margin-bottom: 15px; } .modal-label { font-weight: 700; font-weight: bold; display: block; margin-bottom: 5px; } .modal-input { width: 89%; padding: 10px; border-radius: 5px; border: 1px solid #ccc; font-size: 1rem; } .modal-submit { text-align: center; } .modal-button { background-color: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 1rem; transition: background-color 0.3s; } .modal-button:hover { background-color: #45a049; } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="modal.jsx" footer="button" scroll="set-scroll" %}}import { useState } from "react"; import "./modal.css" function Modal({ editData, onUpdate, onClose }) { const [formData, setFormData] = useState(editData); const handleChange = (e) =&gt; { const { name, value } = e.target; setFormData({ ...formData, [name]: value }); }; const handleSubmit = () =&gt; { onUpdate(formData); }; const today = new Date().toISOString().split("T")[0]; return ( &lt;div className="modal"&gt; &lt;div className="modal-content"&gt; &lt;div className="edit-header"&gt; &lt;div className="modal-header"&gt; &lt;h2&gt;Edit Greeting&lt;/h2&gt; &lt;/div&gt; &lt;div className="modal-close"&gt; &lt;button onClick={onClose}&gt;&times;&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;div className="modal-input-group"&gt; &lt;input name="Name" value={formData.Name} onChange={handleChange} className="modal-input" /&gt; &lt;/div&gt; &lt;div className="modal-input-group"&gt; &lt;input name="Message" value={formData.Message} onChange={handleChange} className="modal-input" /&gt; &lt;/div&gt; &lt;div className="modal-input-group"&gt; &lt;input name="BirthDay" type="date" value={formData.BirthDay} onChange={handleChange} className="modal-input" max={today} // Set max date to today /&gt; &lt;/div&gt; &lt;div className="modal-input-group"&gt; &lt;input name="Email" value={formData.Email} onChange={handleChange} className="modal-input" /&gt; &lt;/div&gt; &lt;div className="modal-submit"&gt; &lt;button className="modal-button" onClick={handleSubmit}&gt;Update&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; ); } export default Modal; {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-css line-numbers" header="toggleSwitch.css" footer="button" scroll="set-scroll" %}}.switch { display: block; position: relative; display: inline-block; width: 34px; height: 20px; } .switch input { opacity: 0; width: 0; height: 0; } .slide { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: 0.4s; border-radius: 20px; } .slide:before { position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px; background-color: white; transition: 0.4s; border-radius: 50%; } input:checked + .slide { background-color: #2196f3; } input:checked + .slide:before { transform: translateX(14px); } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="ToggleSwitch.jsx" footer="button" scroll="set-scroll" %}}import "./toggleSwitch.css" function ToggleSwitch({ isChecked, onToggle }) { return ( &lt;label className="switch"&gt; &lt;input type="checkbox" checked={isChecked} onChange={(e) =&gt; onToggle(e.target.checked)} /&gt; &lt;span className="slide round"&gt; &lt;/span&gt; &lt;/label&gt; ); } export default ToggleSwitch; {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-css line-numbers" header="topBar.css" footer="button" scroll="set-scroll" %}}body{ margin: 0; padding: 0; } .topbarContainer{ height: 50px; width: 100%; background-color: rgb(58, 58, 98); display: flex; align-items: center; position: fixed; top: 0; z-index: 999; } .topbarLeft{ flex: 1 1; display: flex; align-items: center; gap: 22%; padding-left: 29px; } .naviagtion-btn{ height: 30px; background-color: white; border: none; border-radius: 7px; font-size: 14px; font-weight: 600; } .topLogo{ font-size: 24px; margin-left: 15px; color: white; cursor: pointer; } .topBarCenter{ flex: 1 1; color: white; padding-left: 8%; } .topbarRight{ flex: 0 1; display: flex; justify-content: space-between; padding-left: 24%; } .topRight-btn{ width: 74px; height: 31px; font-size: 15px; font-weight: 650; background-color: red; color: white; margin-right: 40px; border: none; border-radius: 7px; } .topbarlink{ display: flex; margin-left: 67px; color: white; } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="TopBar.jsx" footer="button" scroll="set-scroll" %}}import React, { useCallback } from 'react' import "./topBar.css" import { Link } from 'react-router-dom' export default function TopBar() { const logout = useCallback(() =&gt; { window.catalyst.auth.signOut('/'); }, []); return ( &lt;&gt; &lt;div className="topbarContainer"&gt; &lt;div className="topbarLeft"&gt; &lt;Link to="/addUserPage"&gt; &lt;button className='naviagtion-btn'&gt;Add New Greetings&lt;/button&gt; &lt;/Link&gt; &lt;Link to="/ListUsers"&gt; &lt;button className='naviagtion-btn'&gt;Greetings List&lt;/button&gt; &lt;/Link&gt; &lt;/div&gt; &lt;div className="topBarCenter"&gt; &lt;div className="seacrhbar"&gt; &lt;h1&gt;Birthday Greetings&lt;/h1&gt; &lt;/div&gt; &lt;/div&gt; &lt;div className="topbarRight"&gt; &lt;button className="topRight-btn" onClick={logout}&gt;Log Out&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;/&gt; ) } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-css line-numbers" header="Userlist.css" footer="button" scroll="set-scroll" %}}.users-container { display: flex; flex-direction: column; gap: 8px; } .users-lists { width: 88%; padding: 15px; border: 1px solid #ccc; border-radius: 22px; background-color: #f9f9f9; margin-left: 80px; margin-bottom: 34px; display: flex; flex-direction: column; align-items: flex-start; } .user-item { width: 100%; display: flex; align-items: center; justify-content: space-between; } .left-content { display: flex; flex-direction: column; } .left-content span { margin-bottom: 8px; } .name { font-size: 28px; font-weight: bold; } .date, .email { font-size: 16px; } .message { font-size: 20px; } .right-button { display: flex; align-items: center; gap: 20px; width: 25%; justify-content: flex-end; } .edit-btn, .delete-btn { width: 70px; height: 34px; border-radius: 10px; color: white; border: none; } .edit-btn { background-color: black; } .delete-btn { background-color: rgb(255, 0, 0); } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="UserList.jsx" footer="button" scroll="set-scroll" %}}import ToggleSwitch from "./ToggleSwitch"; import "./UserList.css"; function UserList({ users, onEdit, onDelete, onToggle }) { return ( &lt;div className="users-container"&gt; {users.map((user) =&gt; ( &lt;div className="users-lists" key={user.ID}&gt; &lt;div className="user-item"&gt; &lt;div className="left-content"&gt; &lt;span className="name"&gt;{user.Name}&lt;/span&gt; &lt;span className="date"&gt;{user.BirthDay}&lt;/span&gt; &lt;span className="message"&gt;{user.Message}&lt;/span&gt; &lt;span className="email"&gt;{user.Email}&lt;/span&gt; &lt;/div&gt; &lt;div className="right-button"&gt; &lt;ToggleSwitch isChecked={user.AutoSend} onToggle={(checked) =&gt; onToggle(user.ID, checked)} /&gt; &lt;button className="edit-btn" onClick={() =&gt; onEdit(user.ID)}&gt;Edit&lt;/button&gt; &lt;button className="delete-btn" onClick={() =&gt; onDelete(user.ID)}&gt;Delete&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; ))} &lt;/div&gt; ); } export default UserList; {{%/panel_with_adjustment%}} Copy the code given below and paste it in the respective files located in the client directory ({{%badge%}}birthdaygreetings/src/{{%/badge%}}) using an IDE and save the file. {{%panel_with_adjustment class="language-css line-numbers" header="App.css" footer="button" scroll="set-scroll" %}}body { margin: 0; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background-color: #ffffff; height: inherit; } .inputs { width: 90%; padding: 15px; margin: 15px 0 22px 0; display: inline-block; border: none; background: #f1f1f1; border-radius: 8px; } h1, h2, h3, h4, h5, h6, p { margin: 1; } #login { height: 29rem; } .Header{ display: flex; align-items: center; gap: 54%; } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="App.js" footer="button" scroll="set-scroll" %}}import { HashRouter, Routes, Route, Navigate } from "react-router-dom"; import Layout from "./Layout"; import Signup from "./SignUp"; import ListUsers from "./ListUsers"; import UserProfile from "./UserProfile"; function App() { return ( &lt;HashRouter&gt; &lt;Routes&gt; &lt;Route path="/" element={&lt;Layout /&gt;} /&gt; &lt;Route path="/signup" element={&lt;Signup /&gt;} /&gt; &lt;Route path="/addUserPage" element={&lt;UserProfile /&gt;} /&gt; &lt;Route path="/ListUsers" element={&lt;ListUsers /&gt;} /&gt; &lt;Route path="" element={&lt;Navigate to="/" replace /&gt;} /&gt; &lt;Route path="*" element={&lt;Navigate to="/" replace /&gt;} /&gt; &lt;/Routes&gt; &lt;/HashRouter&gt; ); } export default App; {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-css line-numbers" header="index.css" footer="button" scroll="set-scroll" %}}body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="index.js" footer="button" scroll="set-scroll" %}}import "./App.css"; import React from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; const container = document.getElementById("root"); const root = createRoot(container); root.render( &lt;React.StrictMode&gt; &lt;App /&gt; &lt;/React.StrictMode&gt; ); {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="Layout.js" footer="button" scroll="set-scroll" %}}import UserProfile from "./UserProfile"; import LoginPage from "./LoginPage.js"; import { useEffect, useState } from "react"; function Layout() { const [isFetching, setIsFetching] = useState(true); const [isUserAuthenticated, setIsUserAuthenticated] = useState(false); const [userDetails, setUserDetails] = useState({ firstName: "", lastName: "", mailid: "", timeZone: "", createdTime: "", }); useEffect(() =&gt; { window.catalyst.auth.isUserAuthenticated().then((result) =&gt; { setUserDetails({ firstName: result.content.first_name, lastName: result.content.last_name, mailid: result.content.email_id, timeZone: result.content.time_zone, createdTime: result.content.created_time, }); setIsUserAuthenticated(true); }) .catch((err) =&gt; {}) .finally(() =&gt; { setIsFetching(false); }); }, []); return ( &lt;&gt; {isFetching ? ( &lt;p&gt;Loading ….&lt;/p&gt; ) : isUserAuthenticated ? ( &lt;UserProfile userDetails={userDetails} /&gt; ) : ( &lt;LoginPage /&gt; )} &lt;/&gt; ); } export default Layout; {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="ListUsers.js" footer="button" scroll="set-scroll" %}}import { useState, useEffect } from "react"; import axios from "axios"; import UserList from "./components/UserList"; import Modal from "./components/modal"; import TopBar from "./components/TopBar"; function ListUsers() { const [users, setUsers] = useState([]); const [editData, setEditData] = useState(null); const [isModelOpen, setModal] = useState(false); const fetchUsers = async () =&gt; { try { const response = await axios.get("/server/advance_function/getReminder"); setUsers(response.data); } catch (error) { console.error("Error fetching users:", error); } }; useEffect(() =&gt; { fetchUsers(); }, []); const handleEdit = (id) =&gt; { const userToEdit = users.find((user) =&gt; user.ID === id); setEditData(userToEdit); setModal(true); }; const handleUpdate = async (updatedData) =&gt; { try { await axios.put("/server/advance_function/updateReminder", updatedData); alert("User updated successfully!"); setEditData(null); setModal(false); fetchUsers(); } catch (error) { console.error("Error updating user:", error); } }; const handleDelete = async (id) =&gt; { if (window.confirm("Are you sure you want to delete this birthday wish?")) { try { await axios.delete(`/server/advance_function/deleteReminder/${id}`); setUsers(users.filter((user) =&gt; user.ID !== id)); alert("User deleted successfully!"); } catch (error) { console.error("Error deleting user:", error); } } }; const handleToggle = async (ID, checked) =&gt; { const AutoSendStatus = checked ? "enable" : "disable"; try { await axios.patch("/server/advance_function/toggleAutoSend", { id: ID, status: AutoSendStatus, }); setUsers((prevUsers) =&gt; prevUsers.map((user) =&gt; user.ID === ID ? { ...user, AutoSend: checked } : user ) ); } catch (error) { console.error("Error toggling the user:", error); } }; return ( &lt;&gt; &lt;div &gt; &lt;TopBar /&gt; &lt;/div&gt; &lt;div className="userlist-div" style={{height: "calc(100vh - 60px)",margin: "60px 0 0"}}&gt; &lt;UserList users={users} onEdit={handleEdit} onDelete={handleDelete} onToggle={handleToggle} /&gt; {isModelOpen && ( &lt;Modal editData={editData} onUpdate={handleUpdate} onClose={() =&gt; setModal(false)} /&gt; )} &lt;/div&gt; &lt;/&gt; ); } export default ListUsers; {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="LoginPage.js" footer="button" scroll="set-scroll" %}}import { useEffect } from "react"; import { Link } from "react-router-dom"; const LoginPage = () => { useEffect(() => { // Configuration is optional const config = { css_url: "/app/embeddediframe.css", // Login page customization CSS file path, if not provided default CSS will be rendered is_customize_forgot_password: false, // Default value is false. Set to true to customize Forgot Password page forgot_password_id: "login", // Element ID where the Forgot Password page should be loaded, defaults to "loginDivElementId" forgot_password_css_url: "/app/fpwd.css", // Forgot Password page customization CSS file path, if not provided default CSS will be rendered }; window.catalyst.auth.signIn("login", config); }, []); return ( &lt;div className="container"&gt; &lt;center&gt; &lt;h1 className="title">Birthday Greetings&lt;/h1&gt; &lt;/center&gt; &lt;div id="login">&lt;/div&gt; &lt;p className="homepage"&gt; &lt;b&gt; Don't have an account?{" "} &lt;Link to="/signup" style={{ color: "blue", textDecorationLine: "underline" }}&gt; Sign-up &lt;/Link&gt;{" "} now! &lt;/b&gt; &lt;/p&gt; &lt;/div&gt; ); }; export default LoginPage; {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-css line-numbers" header="SignUp.css" footer="button" scroll="set-scroll" %}}input[type="submit"] { background-color: green; color: white; width: 40%; padding: 8px; border-radius: 8px; text-align: center; border: none; cursor: pointer; } #link { font-size: medium; color: blue; } .modal-content { background-color: #c0c0c0; margin: 30px auto; } .signupfnbtn { width: 30%; padding: 8px; border-radius: 8px; background-color: green; height: 100%; } p { text-align: center; font-size: medium; font-weight: bold; } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="SignUp.js" footer="button" scroll="set-scroll" %}}import "./SignUp.css"; import React, { useState } from "react"; function Signup() { const [displayText, setDisplayText] = useState(""); const [form, setForm] = useState({ firstName: "", lastName: "", email: "", }); const [showForm, setShowForm] = useState(true); const handleSubmit = async (event) =&gt; { event.preventDefault(); setShowForm(false); setDisplayText( "An account verification email has been sent to your email address" ); const data = { first_name: form.firstName, last_name: form.lastName, email_id: form.email, platform_type: "web", }; try { const auth = window.catalyst.auth; const signupResponse = await auth.signUp(data); if (signupResponse.status === 200) { setTimeout(() =&gt; { window.location.href = "index.html"; }, 3000); } else { alert(signupResponse.message); } } catch (error) { console.error("Error during signup:", error); alert("An error occurred during signup. Please try again."); setShowForm(true); // Show the form again if there's an error } }; const handleInputChange = (e) =&gt; { const { name, value } = e.target; setForm((prevForm) =&gt; ({ ...prevForm, [name]: value, })); }; return ( &lt;div id="signup" className="signup"&gt; {showForm ? ( &lt;div&gt; &lt;center&gt; &lt;img width="80px" height="80px" src="https://cdn2.iconfinder.com/data/icons/user-management/512/profile_settings-512.png" alt="User Profile" /&gt; &lt;h1&gt;User Profile Management&lt;/h1&gt; &lt;/center&gt; &lt;form onSubmit={handleSubmit} className="modal-content"&gt; &lt;center&gt; &lt;h1&gt;Sign Up&lt;/h1&gt; &lt;p&gt;Please fill this form to sign up for a new account.&lt;/p&gt; &lt;/center&gt; &lt;label htmlFor="firstName"&gt; &lt;b&gt;First Name&lt;/b&gt; &lt;input name="firstName" className="inputs" placeholder="Enter First Name" value={form.firstName} onChange={handleInputChange} required /&gt; &lt;/label&gt; &lt;label htmlFor="lastName"&gt; &lt;b&gt;Last Name&lt;/b&gt; &lt;input name="lastName" className="inputs" placeholder="Enter Last Name" value={form.lastName} onChange={handleInputChange} required /&gt; &lt;/label&gt; &lt;label htmlFor="email"&gt; &lt;b&gt;Email&lt;/b&gt; &lt;input name="email" className="inputs" placeholder="Enter email address" value={form.email} onChange={handleInputChange} required /&gt; &lt;/label&gt; &lt;p&gt; By creating an account, you agree to our{" "} &lt;a href="https://www.zoho.com/catalyst/terms.html" target="_blank" rel="noopener noreferrer" id="link" &gt; Terms & Conditions &lt;/a&gt; . &lt;/p&gt; &lt;center&gt; &lt;input type="submit" value="Sign Up" className="signupfnbtn" /&gt; &lt;/center&gt; &lt;/form&gt; &lt;/div&gt; ) : ( &lt;p&gt;{displayText}&lt;/p&gt; )} &lt;/div&gt; ); } export default Signup; {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-css line-numbers" header="UserProfile.css" footer="button" scroll="set-scroll" %}}.app-container{ overflow: auto; } ::-webkit-scrollbar{ width: 5px; } ::-webkit-scrollbar-track{ background-color: rgb(34, 34, 59); } ::-webkit-scrollbar-thumb{ background-color: rgb(150, 150, 150); } .Header{ display: flex; align-items: center; gap: 54%; } {{%/panel_with_adjustment%}} {{%panel_with_adjustment class="language-js line-numbers" header="UserProfile.js" footer="button" scroll="set-scroll" %}}import "./UserProfile.css"; import axios from "axios"; import Form from "./components/form"; import TopBar from "./components/TopBar"; function UserProfile() { const handleSubmit = async (formData) =&gt; { try { await axios.post("/server/advance_function/insertReminder",formData); alert("Reminder added successfully!"); window.location.reload(); } catch (error) { console.error("Error submitting form:", error); } }; return ( &lt;div className="app-container"&gt; &lt;div className="Header"&gt; &lt;TopBar /&gt; &lt;/div&gt; &lt;div className="form-div"&gt; &lt;Form onSubmit={handleSubmit} /&gt; &lt;/div&gt; &lt;/div&gt; ); } export default UserProfile; {{%/panel_with_adjustment%}} Update the {{%badge%}}client-package.json{{%/badge%}} file present in the {{%badge%}}birthdaygreetings/{{%/badge%}} directory as shown below. {{%panel_with_adjustment class="language-json line-numbers" header="client-package.json" footer="button" scroll="set-scroll" %}} { "name": "birthdaygreetings", "version": "0.0.1", "homepage": "index.html", "login_redirect": "index.html" } {{%/panel_with_adjustment%}} The client directory is now configured. Let’s go over the business logic of the application. - **Authentication**: - The first page of the application will be the login page. Along with the login page, the user will be provided with the option to create a new account or reset the password of an existing account. - The user will only be able to create an account if they satisfy the condition coded in the Custom User Validation function. - When they successfully register for an account, they will have to verify their account using the verification link sent to their email. - Once they have verified their account and created a valid password, they will be allowed to log in to the app and begin using the application. - **Scheduling the cron** (reminder creation): - The end user will now create a birthday greeting by providing the following details: - Name - Date of birth - Custom message - Email address of the recipient - Once these details are submitted, the Dynamic Cron will be created, and it will be scheduled to submit a recurring job every year to the job pool on the date of the birthday. - **Manage the reminders**: - After creating the required reminders, you can view a list of reminders in the application. You can also disable, edit, and delete reminders. - The disable action is handled using a toggle. By default, it will be enabled. If disabled, or updated in any manner, the change will be updated in the Data Store, and the Dynamic Cron will be automatically adjusted per the new requirement. - **Log out functionality**: - Finally, once you have completed all of the required operations, you can safely log out of the application. -------------------------------------------------------------------------------- title: "Test the Application" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.702Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/test-app/" service: "All Services" related: - CLI Help Documentation (/en/cli/v1/cli-command-reference/) - Serve Resources (/en/cli/v1/serve-resources/introduction/) -------------------------------------------------------------------------------- # Test the Application Before you deploy the application to the remote console, you can {{%link href="/en/cli/v1/serve-resources/introduction" %}}test the application{{%/link%}} on a local server and check if everything works fine using the Catalyst CLI. To serve the Catalyst project locally, execute the following command from your project directory: {{%cli%}}catalyst serve{{%/cli%}} <br /> The Birthday Greetings application will now be served at default {{%badge%}}port 3000{{%/badge%}}. The local endpoint URLs of the components are displayed. <br /> {{%info%}}{{%bold%}}Info:{{%/bold%}} Every time you access the home page or any of the sub-pages of your client or the function, the CLI will display a live log of the URL accessed, along with the HTTP request method.{{%/info%}} You can now open the application in a browser using the local URL of the client displayed in the CLI. <br /> ### Sign Up/Sign In When you serve the application, the first page you will encounter is the Sign In page. Since you have not signed up for the application, click the **Sign Up** option. <br /> Provide the required details and click the **Sign Up** button. <br /> {{%note%}}{{%bold%}}Note:{{%/bold%}} Ensure that you create the account in a manner that satisfies the authentication logic you coded in the Custom User Validation function.{{%/note%}} You will have to verify your account using the link sent to you via email. The content of the email will be the one you configured using the Email Templates feature. <br /> Once you click the link, you will be asked to create a password. <br /> Your account will be registered to the application, and this can be confirmed in the **User Management** section in the Authentication component. You will also be able to see that the App User role will be assigned to the account. <br /> You will now be able to access the application. ### Create Greetings Enter the required details in the client app and click **Submit** to create the required greeting. <br /> This action will have created a Dynamic Cron in the Job Scheduling service. Navigate to the cron component present in the Job Scheduling service to view the cron details. <br /> Click the cron to view its details. <br /> From these details, we can understand that the cron has been scheduled to submit a job to the job pool at 12 AM on the date of the birthday. However, for our testing purposes, you can click the **Submit Job** button to submit a job to the job pool instantly. <br /> You can check the status of the job by clicking the **Execution History** button. <br /> {{%info%}}{{%bold%}}Info{{%/bold%}}: If a failure status occurs, click the {{%bold%}}View Logs{{%/bold%}} button. This will direct you to the {{%link href="/en/devops/help/logs/introduction/" %}}Logs{{%/link%}} component present in the {{%link href="/en/devops/" %}}Catalyst DevOps{{%/link%}} service where you can view detailed logs to aid your debugging process.{{%/info%}} Now if you check the email configured as the receiver, you will have received the birthday wish from the sender email address you configured using the Mail component. <br /> ### Manage Reminders You can click the **Greetings List** button to view the list of reminders you have configured. <br /> You can edit the reminders by clicking the **Edit** button. <br /> Click **Update** to apply the required changes. You can disable the cron by clicking the toggle to disable the wish from being sent out. You can also delete the greeting by clicking the **Delete** button. <br /> All the changes made in the application will be reflected in the Data Store, and the cron will be adjusted accordingly. The application is now working as intended. -------------------------------------------------------------------------------- title: "Deploy the Application" description: "Build a birthday greetings application using the Job Scheduling service and automatically send out birthday greetings to the required person via their email." last_updated: "2026-03-18T07:41:08.706Z" source: "https://docs.catalyst.zoho.com/en/tutorials/birthday-greetings/nodejs/deploy-app/" service: "All Services" related: - CLI Help Documentation (/en/cli/v1/cli-command-reference/) - Deploy Resources (/en/cli/v1/deploy-resources/introduction/) -------------------------------------------------------------------------------- # Deploy the Application You can now deploy the application to the remote console. To {{%link href="/en/cli/v1/deploy-resources/introduction" %}}deploy your Catalyst project from the CLI{{%/link%}}, run the following command in your terminal from your project directory: {{%cli%}}catalyst deploy{{%/cli%}} The function is deployed first, followed by the client component. The app URLs of the components will be displayed. <br /> The application is now deployed to the Catalyst console. Now, if you navigate to the *Web Client Hosting* component located in the *Cloud Scale* section, you will be able to acquire the application’s invocation URL. <br /> You can use the invocation URL in a browser to access the deployed application. The Birthday Greetings application can now be accessed from its {{%link href="/en/cloud-scale/help/web-client-hosting/introduction/" %}}web app URL{{%/link%}}. <br />