Compare commits
10 commits
bd29eefcef
...
e360003061
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e360003061 | ||
![]() |
c4d3c70f8c | ||
![]() |
777409e6cc | ||
![]() |
4dbee045fc | ||
![]() |
6b844ceb1b | ||
![]() |
4d033228b1 | ||
![]() |
ce57fdd5c6 | ||
![]() |
add47667af | ||
![]() |
151e179300 | ||
![]() |
134e7d12e3 |
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -41,7 +41,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-react": "^8.3.0",
|
||||
"input-otp": "^1.2.4",
|
||||
"lucide-react": "^0.462.0",
|
||||
|
@ -4196,9 +4196,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-react": "^8.3.0",
|
||||
"input-otp": "^1.2.4",
|
||||
"lucide-react": "^0.462.0",
|
||||
|
|
|
@ -1,42 +1,80 @@
|
|||
|
||||
import React from "react";
|
||||
import { User } from "lucide-react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
|
||||
interface Conversation {
|
||||
interface Message {
|
||||
id: string;
|
||||
contact: string;
|
||||
lastMessage: string;
|
||||
timestamp: string;
|
||||
phone_number: string;
|
||||
message: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
const mockConversations: Conversation[] = [
|
||||
{
|
||||
id: "1",
|
||||
contact: "+1 (555) 987-6543",
|
||||
lastMessage: "Thanks for your message",
|
||||
timestamp: "2 min ago",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
contact: "+1 (555) 876-5432",
|
||||
lastMessage: "I'll get back to you shortly",
|
||||
timestamp: "1 hour ago",
|
||||
},
|
||||
];
|
||||
|
||||
export function ConversationList() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: messages, isLoading } = useQuery({
|
||||
queryKey: ['messages'],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await supabase
|
||||
.from('messages')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching messages:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data as Message[];
|
||||
},
|
||||
refetchInterval: 5000 // Refetch every 5 seconds
|
||||
});
|
||||
|
||||
// Subscribe to new messages
|
||||
React.useEffect(() => {
|
||||
const subscription = supabase
|
||||
.channel('messages')
|
||||
.on('postgres_changes', {
|
||||
event: '*',
|
||||
schema: 'public',
|
||||
table: 'messages'
|
||||
}, () => {
|
||||
// Trigger a refetch when new messages arrive
|
||||
queryClient.invalidateQueries({ queryKey: ['messages'] });
|
||||
})
|
||||
.subscribe();
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [queryClient]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<p className="text-muted-foreground">Loading conversations...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const conversations = messages || [];
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4 animate-fadeIn">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">Conversations</h2>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{mockConversations.length} active
|
||||
{conversations.length} active
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
{mockConversations.map((conversation) => (
|
||||
{conversations.map((message) => (
|
||||
<Card
|
||||
key={conversation.id}
|
||||
key={message.id}
|
||||
className="p-4 transition-all hover:shadow-md cursor-pointer group"
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
|
@ -45,13 +83,13 @@ export function ConversationList() {
|
|||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex justify-between items-start">
|
||||
<h3 className="font-medium truncate">{conversation.contact}</h3>
|
||||
<h3 className="font-medium truncate">{message.phone_number}</h3>
|
||||
<span className="text-xs text-muted-foreground whitespace-nowrap ml-2">
|
||||
{conversation.timestamp}
|
||||
{formatDistanceToNow(new Date(message.created_at), { addSuffix: true })}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground truncate">
|
||||
{conversation.lastMessage}
|
||||
{message.message}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,17 +5,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { toast } from "sonner";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
// Initialize Supabase client with proper error handling
|
||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
||||
|
||||
if (!supabaseUrl || !supabaseAnonKey) {
|
||||
throw new Error('Supabase URL and Anon Key are required');
|
||||
}
|
||||
|
||||
const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
||||
import { supabase } from "@/lib/supabase";
|
||||
|
||||
export function MessageComposer() {
|
||||
const [message, setMessage] = useState("");
|
||||
|
@ -28,28 +18,35 @@ export function MessageComposer() {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log('Attempting to send message:', { phoneNumber, message });
|
||||
const formattedPhoneNumber = phoneNumber.startsWith('+1') ? phoneNumber : `+1${phoneNumber}`;
|
||||
|
||||
console.log('Attempting to store message:', { phoneNumber: formattedPhoneNumber, message });
|
||||
setIsSending(true);
|
||||
|
||||
try {
|
||||
console.log('Invoking send-sms function...');
|
||||
const { data, error } = await supabase.functions.invoke('send-sms', {
|
||||
body: { phoneNumber, message }
|
||||
});
|
||||
|
||||
console.log('Response from send-sms:', { data, error });
|
||||
// Store the message in Supabase
|
||||
const { data, error } = await supabase
|
||||
.from('messages')
|
||||
.insert([
|
||||
{
|
||||
phone_number: formattedPhoneNumber,
|
||||
message: message
|
||||
}
|
||||
])
|
||||
.select();
|
||||
|
||||
if (error) {
|
||||
console.error('Supabase function error:', error);
|
||||
console.error('Supabase insert error:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
toast.success("Message sent successfully!");
|
||||
console.log('Message stored successfully:', data);
|
||||
toast.success("Message stored successfully!");
|
||||
setMessage("");
|
||||
setPhoneNumber("");
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Detailed error:', error);
|
||||
toast.error("Failed to send message. Please try again.");
|
||||
toast.error(`Failed to store message: ${error.message || 'Please try again'}`);
|
||||
} finally {
|
||||
setIsSending(false);
|
||||
}
|
||||
|
@ -69,7 +66,7 @@ export function MessageComposer() {
|
|||
</label>
|
||||
<Input
|
||||
id="phone"
|
||||
placeholder="Enter phone number..."
|
||||
placeholder="Enter phone number... (e.g. +13109228324)"
|
||||
value={phoneNumber}
|
||||
onChange={(e) => setPhoneNumber(e.target.value)}
|
||||
className="w-full"
|
||||
|
|
11
src/lib/supabase.ts
Normal file
11
src/lib/supabase.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
const supabaseUrl = "https://ikmbnngahzcellthaysf.supabase.co";
|
||||
const supabaseAnonKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImlrbWJubmdhaHpjZWxsdGhheXNmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDA1ODY5NjUsImV4cCI6MjA1NjE2Mjk2NX0.puDSeLAaTirOPyj6ndF2Mzzu8CKxlwJsoFP6gK8cWuA";
|
||||
|
||||
if (!supabaseUrl || !supabaseAnonKey) {
|
||||
throw new Error('Supabase URL and Anon Key are required');
|
||||
}
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
Loading…
Reference in a new issue