Compare commits

..

No commits in common. "e3600030617a55dc61ae5c11a780b2bb1b887cd1" and "bd29eefcef7555dfa2c76c96ded8a53cc6b2c21e" have entirely different histories.

5 changed files with 53 additions and 99 deletions

8
package-lock.json generated
View file

@ -41,7 +41,7 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
"date-fns": "^4.1.0", "date-fns": "^3.6.0",
"embla-carousel-react": "^8.3.0", "embla-carousel-react": "^8.3.0",
"input-otp": "^1.2.4", "input-otp": "^1.2.4",
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
@ -4196,9 +4196,9 @@
} }
}, },
"node_modules/date-fns": { "node_modules/date-fns": {
"version": "4.1.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "github", "type": "github",

View file

@ -44,7 +44,7 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
"date-fns": "^4.1.0", "date-fns": "^3.6.0",
"embla-carousel-react": "^8.3.0", "embla-carousel-react": "^8.3.0",
"input-otp": "^1.2.4", "input-otp": "^1.2.4",
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",

View file

@ -1,80 +1,42 @@
import React from "react";
import { User } from "lucide-react"; import { User } from "lucide-react";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { supabase } from "@/lib/supabase";
import { formatDistanceToNow } from "date-fns";
interface Message { interface Conversation {
id: string; id: string;
phone_number: string; contact: string;
message: string; lastMessage: string;
created_at: string; timestamp: string;
} }
export function ConversationList() { const mockConversations: Conversation[] = [
const queryClient = useQueryClient(); {
id: "1",
const { data: messages, isLoading } = useQuery({ contact: "+1 (555) 987-6543",
queryKey: ['messages'], lastMessage: "Thanks for your message",
queryFn: async () => { timestamp: "2 min ago",
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 {
}); id: "2",
contact: "+1 (555) 876-5432",
// Subscribe to new messages lastMessage: "I'll get back to you shortly",
React.useEffect(() => { timestamp: "1 hour ago",
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 || [];
export function ConversationList() {
return ( return (
<div className="space-y-4 p-4 animate-fadeIn"> <div className="space-y-4 p-4 animate-fadeIn">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-semibold tracking-tight">Conversations</h2> <h2 className="text-2xl font-semibold tracking-tight">Conversations</h2>
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
{conversations.length} active {mockConversations.length} active
</span> </span>
</div> </div>
<div className="grid gap-4"> <div className="grid gap-4">
{conversations.map((message) => ( {mockConversations.map((conversation) => (
<Card <Card
key={message.id} key={conversation.id}
className="p-4 transition-all hover:shadow-md cursor-pointer group" className="p-4 transition-all hover:shadow-md cursor-pointer group"
> >
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
@ -83,13 +45,13 @@ export function ConversationList() {
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<h3 className="font-medium truncate">{message.phone_number}</h3> <h3 className="font-medium truncate">{conversation.contact}</h3>
<span className="text-xs text-muted-foreground whitespace-nowrap ml-2"> <span className="text-xs text-muted-foreground whitespace-nowrap ml-2">
{formatDistanceToNow(new Date(message.created_at), { addSuffix: true })} {conversation.timestamp}
</span> </span>
</div> </div>
<p className="text-sm text-muted-foreground truncate"> <p className="text-sm text-muted-foreground truncate">
{message.message} {conversation.lastMessage}
</p> </p>
</div> </div>
</div> </div>

View file

@ -5,7 +5,17 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { toast } from "sonner"; import { toast } from "sonner";
import { supabase } from "@/lib/supabase"; 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);
export function MessageComposer() { export function MessageComposer() {
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
@ -18,35 +28,28 @@ export function MessageComposer() {
return; return;
} }
const formattedPhoneNumber = phoneNumber.startsWith('+1') ? phoneNumber : `+1${phoneNumber}`; console.log('Attempting to send message:', { phoneNumber, message });
console.log('Attempting to store message:', { phoneNumber: formattedPhoneNumber, message });
setIsSending(true); setIsSending(true);
try { try {
// Store the message in Supabase console.log('Invoking send-sms function...');
const { data, error } = await supabase const { data, error } = await supabase.functions.invoke('send-sms', {
.from('messages') body: { phoneNumber, message }
.insert([ });
{
phone_number: formattedPhoneNumber, console.log('Response from send-sms:', { data, error });
message: message
}
])
.select();
if (error) { if (error) {
console.error('Supabase insert error:', error); console.error('Supabase function error:', error);
throw error; throw error;
} }
console.log('Message stored successfully:', data); toast.success("Message sent successfully!");
toast.success("Message stored successfully!");
setMessage(""); setMessage("");
setPhoneNumber(""); setPhoneNumber("");
} catch (error: any) { } catch (error) {
console.error('Detailed error:', error); console.error('Detailed error:', error);
toast.error(`Failed to store message: ${error.message || 'Please try again'}`); toast.error("Failed to send message. Please try again.");
} finally { } finally {
setIsSending(false); setIsSending(false);
} }
@ -66,7 +69,7 @@ export function MessageComposer() {
</label> </label>
<Input <Input
id="phone" id="phone"
placeholder="Enter phone number... (e.g. +13109228324)" placeholder="Enter phone number..."
value={phoneNumber} value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)} onChange={(e) => setPhoneNumber(e.target.value)}
className="w-full" className="w-full"

View file

@ -1,11 +0,0 @@
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);