🪄✨How I built this Twitter scheduler using React and Hasura🔥

🪄how-i-built-this-twitter-scheduler-using-react-and-hasura

TL;DR 🔥

In this tutorial, you’ll learn how to build a tweet scheduler. Enter your Twitter credentials and select a time and a date and your tweet will be tweeted at that time.

Rocketgraph: An open-source replacement to Firebase 🚀

Just a quick background about us. Rocketgraph lets developers build web applications in minutes.
We do this by providing:

  1. PostgresDB hosted on AWS RDS
  2. Hasura console to GraphQLise your PostgresDB, manage your permissions, crons, schedulers and a lot more.
  3. Built-in authentication that supports email/password, social, magic link and OTP
  4. Serverless functions for any additional backend code.

Before we start

We are going to be using Hasura on-off schedulers to trigger Twitter API at the appropriate time and Rocketgraph’s edge functions to setup the backend. So we need to create a project on Rocketgraph. It’s free.

Signup

And create a project

It will setup a Hasura console. For now you don’t need to know how to use it. Since we will be using it via the Hasura API.

Let’s set it up 🆙

We’ll be using React.js and shadcn UI for the front-end and Node.js for our serverless functions.

create a folder for project, client and server:

mkdir twitter-app && cd twitter-app
mkdir client server

Setting up Lambda functions

With Rocketgraph you can easily create lambda functions that act as your backend. To setup, note the project name from your project console on Rocketgraph:

npm init -y
npm install express body-parser axios axios-oauth-1.0a
npm install @codegenie/serverless-express

The last install is for express on the AWS Lambdas. You don’t need to worry about that as Rocketgraph takes care of Lambda deployments.

Create a file index.js and add the following code

const serverlessExpress = require('@codegenie/serverless-express')
const app = require('./app')
exports.handler = serverlessExpress({ app })

Now create a file named app.js and add the following code:

// jshint esversion: 6

const express = require("express");
const bodyParser = require("body-parser");
const axios = require("axios");
const addOAuthInterceptor = require("axios-oauth-1.0a");

const app = express();
const port = 3000

app.use(express.json());

const APP_URL = "https://guhys2s4uv24wbh6zoxl3by4xa0im.lambda-url.us-east-2.on.aws/tweet";



app.post("/tweet", function (req, res) {
    // Create a client whose requests will be signed
    const client = axios.create();
    // Specify the OAuth options
    const payload = req.body.payload;
    const options = {
        algorithm: 'HMAC-SHA1',
        key: `${payload.key}`,
        secret: `${payload.secret}`,
        token: `${payload.token}`,
        tokenSecret: `${payload.tokenSecret}`,
    };

    const data = {
        "text": `${payload.tweet}`
    };

    console.log("addOAuthInterceptor is not a function: ", addOAuthInterceptor)
    // Add interceptor that signs requests
    addOAuthInterceptor.default(client, options);

    console.log("params: ", req.body);
    client.post("https://api.twitter.com/2/tweets", 
    data)
    .then(response => {
        console.log("resp: ", response, req.body.key);
        res.status(200).send(`

the result is : Tweeted

`
); }) .catch(err => { console.log(err); res.status(500).send(`

the result is : Error

`
); }); }); app.post("/schedule-tweet", function (req, res) { const body = JSON.parse(req.body); console.log("body", body) const jsonb = { "type": "create_scheduled_event", "args": { "webhook": `${APP_URL}`, "schedule_at": `${body.schedule_at}`, "include_in_metadata": true, "payload": { "tweet": `${body.tweet}`, "algorithm": 'HMAC-SHA1', "key": `${body.key}`, "secret": `${body.secret}`, "token": `${body.token}`, "tokenSecret": `${body.tokenSecret}`, }, "comment": "sample scheduled event comment" } } console.log(req.body); axios.post("https://hasura-vlklxvo.rocketgraph.app/v1/metadata", jsonb, { "headers" : { "Content-Type": "application/json", "X-Hasura-Role":"admin", "X-hasura-admin-secret":"********" }, }).then(function (response) { console.log("response: ", response); res.status(200).send({ message: "Your result is tweeted" }) }).catch(function (error) { console.log("Error from Hasura: ", error); res.status(500).send(error) }); }) app.listen(port, () => console.log(`calculator listening on port ${port}!`)) module.exports = app;

Notice the APP_URL? Leave it empty for now. We will get this URL once we deploy the function to prod. So now let’s go ahead and do that.

Also replace X-hasura-admin-secret and HASURA_URL with the ones shown on your project’s Hasura tab

Deploy the function ⛈️

Rgraph provides a cute CLI to assist you to develop your functions to AWS lambda:

npm install -g rgraph@0.1.9

And with your project name, execute this to deploy your function:

rgraph deploy 

Build the frontend 👼

Create project

cd client
npx create-next-app@latest my-app --typescript --tailwind --eslint

Add shadcn ui

npx shadcn-ui@latest init

Add necessary components for our app

npx shadcn-ui@latest add button popover calendar card input label

Create UI

In src/app/page.tsx

"use client"

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import React from "react"
import { useState } from "react"
import { DatePickerDemo } from "./date"


type ScheduleTweetInput = {
  key: String,
  secret: String,
  token: String,
  tokenSecret: String,
  tweet: String,
  date: any,
  time: any,
}

const sendTweet = async (twitterData: ScheduleTweetInput) => {
  const { date, time, tweet, key, secret, token, tokenSecret } = twitterData;
  console.log(time.split(":"))
  date.setHours(...time.split(":"))
  console.log(twitterData, date);
  const isoDate = date.toISOString()
  console.log(isoDate);
  const url = "https://guhys2s4uv24wbh6zoxl3by4xa0imsdb.lambda-url.us-east-2.on.aws/schedule-tweet"
  const postData = {
      "tweet": tweet,
      "schedule_at": isoDate,
      "key": key,
      "secret": secret,
      "token": token,
      "tokenSecret": tokenSecret
  }


  let headersList = {
    "Accept": "*/*",
    "User-Agent": "Thunder Client (https://www.thunderclient.com)",
    "Content-Type": "application/json"
   }

   let bodyContent = JSON.stringify(postData);

   let response = await fetch(url, { 
     method: "POST",
     body: bodyContent,
     headers: headersList,
     mode: "no-cors"
   });

   let data = await response.text();
   console.log(data, response.status, response.json);
   console.log("Status", response.status, response);
}

export default function DemoCreateAccount() {
  const [key, setKey] = useState("");
  const [token, setToken] = useState("");
  const [tokenSecret, setTokenSecret] = useState("");
  const [tweet, setTweet] = useState("");
  const [secret, setSecret] = useState("");
  const [date, setDate] = React.useState<Date>()
  const [time, setTime] = useState<any>('10:00');

  console.log(key, "key")

  return (
    <Card>
      <CardHeader className="space-y-1">
        <CardTitle className="text-2xl">Twitter scheduler</CardTitle>
        <CardDescription>
          Enter your credentials below to schedule a tweet
        </CardDescription>
      </CardHeader>
      <CardContent className="grid gap-4">
        <div className="grid gap-2">
          <Label htmlFor="email">API Key</Label>
          <Input id="email" type="email" placeholder="API Key" onChange={(e) => setKey(e.target.value)}/>
        </div>
        <div className="grid gap-2">
          <Label htmlFor="email">API Secret</Label>
          <Input id="email" type="email" placeholder="API Secret" onChange={(e) => setSecret(e.target.value)}/>
        </div>
        <div className="grid gap-2">
          <Label htmlFor="email">Access Token</Label>
          <Input id="email" type="email" placeholder="Access Token" onChange={(e) => setToken(e.target.value)} />
        </div>
        <div className="grid gap-2">
          <Label htmlFor="email">Access Secret</Label>
          <Input id="email" type="email" placeholder="Access Secret" onChange={(e) => setTokenSecret(e.target.value)}/>
        </div>
        <div className="grid gap-2">
          <Label htmlFor="email">Tweet</Label>
          <Input id="email" type="email" placeholder="Tweet your tweet here" onChange={(e) => setTweet(e.target.value)}/>
        </div>
        {DatePickerDemo(setDate, date)}
        <input aria-label="Time" type="time" onChange={(e) => setTime(e.target.value)}/>
      </CardContent>
      <CardFooter>
        <Button className="w-full" onClick={() => sendTweet({
          key,
          token,
          tokenSecret,
          date,
          time,
          tweet,
          secret,
        })}>Schedule tweet</Button>
      </CardFooter>
    </Card>
  )
}

Create a file called src/app/date.tsx

"use client"

import * as React from "react"
import { CalendarIcon } from "@radix-ui/react-icons"
import { format } from "date-fns"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover"

export function DatePickerDemo(onChange: any, date: any) {

  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button
          variant={"outline"}
          className={cn(
            "w-[240px] justify-start text-left font-normal",
            !date && "text-muted-foreground"
          )}
        >
          <CalendarIcon className="mr-2 h-4 w-4" />
          {date ? format(date, "PPP") : <span>Pick a date</span>}
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-auto p-0" align="start">
        <Calendar
          mode="single"
          selected={date}
          onSelect={onChange}
          initialFocus
        />
      </PopoverContent>
    </Popover>
  )
}
Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
lowercase-saves-data?-with-huffman-encoding-ex.

lowercase saves data? with Huffman encoding ex.

Next Post
animated-sidebar-made-using-rasengan-and-tailwind

Animated Sidebar made using Rasengan and Tailwind

Related Posts