13## Setup
14
151. **WhatsApp Business API Setup:**
16 - Sign up for WhatsApp Business API through Meta Business
17 - Get your access token and phone number ID
18 - Set webhook URL to: `https://your-val-url.web.val.run/webhook`
202. **Environment Variables:**
21 Set these in your Val Town environment:
22 - `WHATSAPP_ACCESS_TOKEN`: Your WhatsApp Business API access token
23 - `WHATSAPP_PHONE_NUMBER_ID`: Your WhatsApp Business phone number ID
24 - `WEBHOOK_VERIFY_TOKEN`: A secret token for webhook verification
39โ โโโ routes/
40โ โโโ webhook.ts # WhatsApp webhook handler
41โ โโโ api.ts # API routes for frontend
42โ โโโ static.ts # Static file serving
43โโโ frontend/
55
561. Access the web interface to configure your auto-reply settings
572. Set up your WhatsApp Business API webhook
583. Messages will be automatically replied to based on your configuration
594. Monitor message history and analytics through the dashboard
60
61## API Endpoints
62
63- `GET /` - Web interface
64- `POST /webhook` - WhatsApp webhook endpoint
65- `GET /webhook` - Webhook verification
66- `GET /api/settings` - Get current settings
67- `POST /api/settings` - Update settings
68- `GET /api/messages` - Get message history
17โ โ โโโ queries.ts # Database query functions
18โ โโโ routes/
19โ โ โโโ jobs.ts # Job posting API routes
20โ โ โโโ chat.ts # Chat API routes
21โ โโโ index.ts # Main Hono server
22โโโ frontend/
32```
33
34## API Endpoints
35
36- `GET /api/jobs` - Get all job postings
37- `POST /api/jobs` - Create a new job posting
38- `GET /api/chat/messages` - Get chat messages
39- `POST /api/chat/messages` - Send a chat message
40
41## Getting Started
16await runMigrations();
17
18// API routes
19app.route("/api/jobs", jobsRouter);
20app.route("/api/chat", chatRouter);
21
22// Serve static files
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import React, { useState, useEffect, useRef } from "https://esm.sh/react@18.2.0";
3import type { ChatMessage, CreateMessageRequest, ApiResponse } from "../../shared/types.ts";
4
5interface ChatRoomProps {
40 const refreshMessages = async () => {
41 try {
42 const response = await fetch('/api/chat/messages');
43 const result: ApiResponse<ChatMessage[]> = await response.json();
44
45 if (result.success && result.data) {
87 };
88
89 const response = await fetch('/api/chat/messages', {
90 method: 'POST',
91 headers: {
95 });
96
97 const result: ApiResponse<ChatMessage> = await response.json();
98
99 if (result.success && result.data) {
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import React, { useState, useEffect, useRef } from "https://esm.sh/react@18.2.0";
3import type { ChatMessage, User, ApiResponse } from "../../shared/types.ts";
4
5interface ChatProps {
35 const fetchMessages = async () => {
36 try {
37 const response = await fetch('/api/chat/messages');
38 const data: ApiResponse<ChatMessage[]> = await response.json();
39
40 if (data.success && data.data) {
49
50 const connectToChat = () => {
51 const eventSource = new EventSource('/api/chat/stream');
52 eventSourceRef.current = eventSource;
53
91
92 try {
93 const response = await fetch('/api/chat/messages', {
94 method: 'POST',
95 headers: {
100 });
101
102 const data: ApiResponse = await response.json();
103
104 if (!data.success) {
7 <script src="https://cdn.twind.style" crossorigin></script>
8 <script src="https://esm.town/v/std/catch"></script>
9 <link rel="preconnect" href="https://fonts.googleapis.com">
10 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12 <style>
13 body {
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import React, { useState, useEffect } from "https://esm.sh/react@18.2.0";
3import type { Job, User, ApiResponse } from "../../shared/types.ts";
4
5interface JobBoardProps {
21 const fetchJobs = async () => {
22 try {
23 const url = filter === 'all' ? '/api/jobs' : `/api/jobs?status=${filter}`;
24 const response = await fetch(url);
25 const data: ApiResponse<Job[]> = await response.json();
26
27 if (data.success && data.data) {
280 const skills = formData.skills_required.split(',').map(s => s.trim()).filter(s => s);
281
282 const response = await fetch('/api/jobs', {
283 method: 'POST',
284 headers: {
295 });
296
297 const data: ApiResponse = await response.json();
298
299 if (data.success) {
532 <div className="bg-gray-50 rounded-lg p-4">
533 <div className="text-2xl font-bold text-green-600">{formatBudget(job)}</div>
534 <div className="text-sm text-gray-500 capitalize">{job.budget_type} project</div>
535 </div>
536 </div>
539 <h3 className="text-lg font-semibold text-gray-900 mb-3">Status</h3>
540 <div className="bg-gray-50 rounded-lg p-4">
541 <div className="text-lg font-medium text-gray-900 capitalize">
542 {job.status.replace('_', ' ')}
543 </div>
611
612 try {
613 const response = await fetch(`/api/jobs/${job.id}/apply`, {
614 method: 'POST',
615 headers: {
623 });
624
625 const data: ApiResponse = await response.json();
626
627 if (data.success) {
2import React, { useState } from "https://esm.sh/react@18.2.0";
3import JobForm from "./JobForm.tsx";
4import type { Job, CreateJobRequest, ApiResponse } from "../../shared/types.ts";
5
6interface JobBoardProps {
16 setLoading(true);
17 try {
18 const response = await fetch('/api/jobs', {
19 method: 'POST',
20 headers: {
24 });
25
26 const result: ApiResponse<Job> = await response.json();
27
28 if (result.success && result.data) {
45
46 try {
47 const response = await fetch(`/api/jobs/${jobId}`, {
48 method: 'DELETE',
49 });
50
51 const result: ApiResponse<{ deleted: boolean }> = await response.json();
52
53 if (result.success) {
24
25 try {
26 const endpoint = isLogin ? '/api/auth/login' : '/api/auth/register';
27 const body = isLogin
28 ? { email: formData.email, password: formData.password }
3import { getChatMessages, createChatMessage } from "../database/queries.ts";
4import { getUserById } from "../database/queries.ts";
5import type { ApiResponse, ChatMessage } from "../../shared/types.ts";
6
7const chat = new Hono();
16 const messages = await getChatMessages(limit);
17
18 return c.json<ApiResponse<ChatMessage[]>>({
19 success: true,
20 data: messages
23 } catch (error) {
24 console.error("Get chat messages error:", error);
25 return c.json<ApiResponse>({
26 success: false,
27 error: "Failed to fetch messages"
37
38 if (!message || message.trim().length === 0) {
39 return c.json<ApiResponse>({
40 success: false,
41 error: "Message cannot be empty"
44
45 if (message.length > 1000) {
46 return c.json<ApiResponse>({
47 success: false,
48 error: "Message too long (max 1000 characters)"
53 const user = await getUserById(currentUser.userId);
54 if (!user) {
55 return c.json<ApiResponse>({
56 success: false,
57 error: "User not found"
68 broadcastMessage(chatMessage);
69
70 return c.json<ApiResponse<ChatMessage>>({
71 success: true,
72 data: chatMessage
75 } catch (error) {
76 console.error("Send chat message error:", error);
77 return c.json<ApiResponse>({
78 success: false,
79 error: "Failed to send message"