Scheduling is one of the critical features of modern applications. It can enable us to run periodic task that can be automated. Task such as such as sending reminders, scheduling posts, updating data, or automating workflows.
So, In this article, we are going to build a scheduler to post articles on dev.to. Although, dev.to has scheduling features but we are going to implement them in our way, which can be used to build any kind of scheduler application.
So, let’s get started.
We are going to use the following tech stack:
That will be enough to build a scheduler application with ease.
Let’s discuss how the application works, which makes it quite easy to understand the flow of the application. Here is the flow one by one:
The building frontend has become quiet lately with a lot of generative AI. One of the such AI we are going to use is bolt.new. Why bolt.new? It can generate complete React applications with dependencies and all the configurations such as tailwindcss. You can directly edit articles using StackBlitz and also deploy the application. If you require you can download the code to run locally. The bonus point is that it integrate with Supabase quite well so you can generate a working React application with the Supbase integration.
I have used it to generate the front. Here are all the pages.
This will handle the page for displaying components and providing the landing page.
function App() { const [posts, setPosts] = useState<ScheduledPost[]>([]); const handleSchedulePost = async (data: CreatePostData) => { // In a real app, this would make an API call to your edge function const newPost: ScheduledPost = { content: data.content, scheduled_time: data.scheduledTime, status: 'pending', title: data.title, tags: data.tags }; const { error } = await supabase .from('scheduled_posts') .insert(newPost) if (error){ alert(`Erorr: ${error}`) return } // setPosts((prev) => [...prev, newPost]); }; const fetchScheduedPost = async () => { const { data, error } = await supabase .from('scheduled_posts') .select() if(error){ alert(`Erorr Fetching Data: ${error}`) return } setPosts(data) } useEffect(() => { fetchScheduedPost() },[]) return ( <div className="min-h-screen bg-gray-50"> <header className="bg-white shadow-sm"> <div className="max-w-4xl mx-auto px-4 py-4"> <div className="flex items-center gap-2"> <Newspaper className="h-8 w-8 text-blue-500" /> <h1 className="text-xl font-bold text-gray-900">Dev.to Post Scheduler</h1> </div> </div> </header> <main className="max-w-4xl mx-auto px-4 py-8"> <div className="grid gap-8 md:grid-cols-2"> <div> <h2 className="text-xl font-semibold text-gray-800 mb-4">Schedule New Post</h2> <PostForm onSubmit={handleSchedulePost} /> </div> <div> <ScheduledPosts posts={posts} /> </div> </div> </main> </div> ); } export default App;
This displays the scheduled articles.
const StatusIcon = ({ status }: { status: ScheduledPost['status'] }) => { switch (status) { case 'posted': return <CheckCircle className="h-5 w-5 text-green-500" />; case 'failed': return <XCircle className="h-5 w-5 text-red-500" />; default: return <Clock3 className="h-5 w-5 text-yellow-500" />; } }; export function ScheduledPosts({ posts }: ScheduledPostsProps) { return ( <div className="space-y-4"> <h2 className="text-xl font-semibold text-gray-800">Scheduled Posts</h2> {posts.length === 0 ? ( <p className="text-gray-500 text-center py-8">No scheduled posts yet</p> ) : ( <div className="space-y-4"> {posts.map((post, index) => ( <div key={index} className="bg-white p-4 rounded-lg shadow-md border border-gray-100" > <div className="flex items-start justify-between"> <div className="flex-1"> <p className="text-gray-800 mb-2">{post.title}</p> <div className="flex items-center gap-4 text-sm text-gray-500"> <div className="flex items-center gap-1"> <Calendar className="h-4 w-4" /> {new Date(post.scheduled_time).toLocaleDateString()} </div> <div className="flex items-center gap-1"> <Clock className="h-4 w-4" /> {new Date(post.scheduled_time).toLocaleTimeString()} </div> </div> </div> <StatusIcon status={post.status} /> </div> </div> ))} </div> )} </div> ); }
This will handle the form where the user can give information about the article.
function App() { const [posts, setPosts] = useState<ScheduledPost[]>([]); const handleSchedulePost = async (data: CreatePostData) => { // In a real app, this would make an API call to your edge function const newPost: ScheduledPost = { content: data.content, scheduled_time: data.scheduledTime, status: 'pending', title: data.title, tags: data.tags }; const { error } = await supabase .from('scheduled_posts') .insert(newPost) if (error){ alert(`Erorr: ${error}`) return } // setPosts((prev) => [...prev, newPost]); }; const fetchScheduedPost = async () => { const { data, error } = await supabase .from('scheduled_posts') .select() if(error){ alert(`Erorr Fetching Data: ${error}`) return } setPosts(data) } useEffect(() => { fetchScheduedPost() },[]) return ( <div className="min-h-screen bg-gray-50"> <header className="bg-white shadow-sm"> <div className="max-w-4xl mx-auto px-4 py-4"> <div className="flex items-center gap-2"> <Newspaper className="h-8 w-8 text-blue-500" /> <h1 className="text-xl font-bold text-gray-900">Dev.to Post Scheduler</h1> </div> </div> </header> <main className="max-w-4xl mx-auto px-4 py-8"> <div className="grid gap-8 md:grid-cols-2"> <div> <h2 className="text-xl font-semibold text-gray-800 mb-4">Schedule New Post</h2> <PostForm onSubmit={handleSchedulePost} /> </div> <div> <ScheduledPosts posts={posts} /> </div> </div> </main> </div> ); } export default App;
Edge Functions are server-side TypeScript functions, distributed globally at the edge—close to your users. They can be used for listening to webhooks or integrating your Supabase project with third parties like Stripe. Edge Functions are developed using Deno.
For running and deploying the edge function locally you need to have the following:
So, after installing this, you can use your frontend code directory or other to create the Supabase Edge Function.
Run the below command to initiate a supabase project:
const StatusIcon = ({ status }: { status: ScheduledPost['status'] }) => { switch (status) { case 'posted': return <CheckCircle className="h-5 w-5 text-green-500" />; case 'failed': return <XCircle className="h-5 w-5 text-red-500" />; default: return <Clock3 className="h-5 w-5 text-yellow-500" />; } }; export function ScheduledPosts({ posts }: ScheduledPostsProps) { return ( <div className="space-y-4"> <h2 className="text-xl font-semibold text-gray-800">Scheduled Posts</h2> {posts.length === 0 ? ( <p className="text-gray-500 text-center py-8">No scheduled posts yet</p> ) : ( <div className="space-y-4"> {posts.map((post, index) => ( <div key={index} className="bg-white p-4 rounded-lg shadow-md border border-gray-100" > <div className="flex items-start justify-between"> <div className="flex-1"> <p className="text-gray-800 mb-2">{post.title}</p> <div className="flex items-center gap-4 text-sm text-gray-500"> <div className="flex items-center gap-1"> <Calendar className="h-4 w-4" /> {new Date(post.scheduled_time).toLocaleDateString()} </div> <div className="flex items-center gap-1"> <Clock className="h-4 w-4" /> {new Date(post.scheduled_time).toLocaleTimeString()} </div> </div> </div> <StatusIcon status={post.status} /> </div> </div> ))} </div> )} </div> ); }
The below command can be used to create the Edge function
export function PostForm({ onSubmit }: PostFormProps) { const [content, setContent] = useState(''); const [title, setTitle] = useState(''); const [tags, setTags] = useState<string[]>(['javascript', 'react']); const [scheduledTime, setScheduledTime] = useState(''); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); onSubmit({ content, title, scheduledTime, tags }); setContent(''); setTitle(''); setScheduledTime(''); setTags([]); }; const handleTagChange = (e: React.ChangeEvent<HTMLSelectElement>) => { const selectedOptions = Array.from(e.target.selectedOptions); const selectedTags = selectedOptions.map(option => option.value); if(tags.length<4){ setTags(prevTags => { const newTags = selectedTags.filter(tag => !prevTags.includes(tag)); return [...prevTags, ...newTags]; }); } }; const removeTag = (tagToRemove: string) => { setTags(tags.filter(tag => tag !== tagToRemove)); }; return ( <form onSubmit={handleSubmit} className="space-y-4 bg-white p-6 rounded-lg shadow-md"> <div> <label htmlFor="title" className="block text-sm font-medium text-gray-700 mb-2"> Post Title </label> <input type="text" > <p>I will provide the whole code as a GitHub repository at the end. </p> <p>Now, let’s look at Supbase Integration.</p> <h2> Supabase </h2> <p>First create an account on supabase, if you don’t have one. You can look at this article to get information about the creating an account on Supbase, Using ChatGPT with Your Own Data using LangChain and Supabase.</p> <p>Create the table scheduled_post. You can use the below SQL code to run in the SQL Editor to create the table or you can create the table with Table Editor.<br> </p> <pre class="brush:php;toolbar:false"> create table public.scheduled_posts ( id serial not null, content text not null, scheduled_time timestamp with time zone not null, status text null default 'pending'::text, created_at timestamp without time zone null default now(), title character varying null, devto_article_id character varying null, posted_at character varying null, tags character varying[] null, error_message character varying null, constraint scheduled_posts_pkey primary key (id) ) tablespace pg_default; create index if not exists idx_scheduled_time_status on public.scheduled_posts using btree (scheduled_time, status) tablespace pg_default;
The above command will create a directory functions/xscheduler inside the supabase. There you can find the index.ts. Edge function uses Deno environment.
The below code is for the edge function:
npx supabase init
For the ENV such as SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are automatically available to you. For DEVTO_ACCESS_TOKEN, you can generate it from here, and go to Project Setting → Edge Functions to add the token. This token will be available in the Deno environment.
You can use this guide for deploying the edge function, which is needed.
Supbase recently updated the Cron job functionality. Now you can use the dashboard to create the corn job previously you had to write code for that. You can create a job that can run the following:
We are going to use the Edge Function, You can add the details of the Edge function such as name and Authorization with the Anon key as a Bearer Token.
Now, that we have created the application let’s look at the working now. Run the fronted with the below command:
supabase functions new xscheduler
Add the details such as Title, Content, Time, and Tags. Once added click on the Schedule Post. The cron job will run every minute once the article’s scheduled time matches the current time. It will be posted.
The article will be posted on the dev.to when the time range matches.
Using the above technique you can build a scheduler application for anything such as X, Instagram, LinkedIn, etc. You can work on it and add functionality such as the following:
You can look into the code of this project on GitHub here.
Creating a scheduler application simplifies automating tasks like posting articles, sending reminders, and managing workflows. Using React for the frontend and Supabase for the backend, we built a scalable solution that leverages databases, cron jobs, and edge functions. This approach can be adapted for various use cases, enabling efficient automation. With these tools, you’re equipped to build powerful scheduler applications tailored to your needs.
I hope this article has provided you with an understanding of the cron job. Thanks for reading the article.
The above is the detailed content of Building a Custom Scheduler Using React and Supabase. For more information, please follow other related articles on the PHP Chinese website!