Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions app/components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Messages from "./chat/Messages";
export interface Conversation {
id: string;
users: { id: string; email: string }[];
type: string;
}

export interface Message {
Expand Down Expand Up @@ -51,8 +52,27 @@ export default function Chat({ user }: { user: User }) {
const channelRef = useRef<RealtimeChannel>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const bottomRef = useRef<HTMLDivElement | null>(null);
const [badWords, setBadWords] = useState<string[]>([]);
const creatingRef = useRef(false);

useEffect(() => {
fetch(
"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/refs/heads/master/en",
)
.then((res) => res.text())
.then((text) => {
const wordsArray = text.split("\n").filter(Boolean);
setBadWords(wordsArray);
});
}, []);

const sanitizeInput = (input: string) => {
if (!badWords.length) return input;

const filter = new RegExp(`\\b(${badWords.join("|")})\\b`, "gi");
return input.replace(filter, "*-?;[]");
};

useEffect(() => {
if (textareaRef.current) {
const el = textareaRef.current;
Expand All @@ -70,7 +90,8 @@ export default function Chat({ user }: { user: User }) {
`
conversation: conversations(
id,
users: conversation_participants!inner(user_id, email)
users: conversation_participants!inner(user_id, email),
type
)
`,
)
Expand All @@ -88,9 +109,15 @@ export default function Chat({ user }: { user: User }) {
id: u.user_id,
email: u.email,
})),
type: convo.type,
};
});
setConversations(convs);

// making sure global conversation is always first
const sortedConvs = convs.sort((a, b) =>
a.type === "global" ? -1 : b.type === "global" ? 1 : 0,
);
setConversations(sortedConvs);
}
};
fetchConversations();
Expand All @@ -117,7 +144,8 @@ export default function Chat({ user }: { user: User }) {
.select(
`
id,
users:conversation_participants!inner(user_id)
users:conversation_participants!inner(user_id),
type
`,
)
.eq("id", row.conversation_id)
Expand All @@ -142,6 +170,7 @@ export default function Chat({ user }: { user: User }) {
id: u.user_id,
email: row.email,
})),
type: convo.type,
},
]);
});
Expand Down Expand Up @@ -282,6 +311,7 @@ export default function Chat({ user }: { user: User }) {
{ id: user.id, email: user.email ?? "" },
{ id: otherUser.user_id, email: otherUser.email ?? "" },
],
type: "private",
},
]);

Expand All @@ -295,7 +325,7 @@ export default function Chat({ user }: { user: User }) {
await supabase.from("messages").insert({
conversation_id: conversationId,
sender_id: user.id,
text: input.slice(0, 1000), // limit to 1000 chars
text: sanitizeInput(input.slice(0, 1000)), // limit to 1000 chars
});

setTimeout(() => {
Expand Down
12 changes: 7 additions & 5 deletions app/components/chat/Conversations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@ export default function Conversations({
${isActive ? "bg-indigo-500 text-white" : "bg-white/5 text-gray-300"}
`}
>
{otherUser?.email[0]?.toUpperCase()}
{conv.type == "global" ? "G" : otherUser?.email[0]?.toUpperCase()}
</div>
<span
className={`text-xs mt-1 ${isActive ? "text-white" : "text-gray-300"}`}
>
{(() => {
const name = otherUser?.email?.split("@")[0] || "";
return name.length > 10 ? name.slice(0, 8) + "..." : name;
})()}
{conv.type == "global"
? "Global"
: (() => {
const name = otherUser?.email?.split("@")[0] || "";
return name.length > 10 ? name.slice(0, 8) + "..." : name;
})()}
</span>
</div>
);
Expand Down
20 changes: 19 additions & 1 deletion app/components/chat/Messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,26 @@ export default function Messages({
</div>
)}
<div>
<span
className={`text-xs font-semibold ${
msg.sender_id === user.id
? "text-indigo-400"
: "text-gray-400"
}`}
>
{
conversations
.find(
(conv) =>
conv.id === msg.conversation_id &&
conv.type === "global",
)
?.users.find((u) => u.id === msg.sender_id)
?.email.split("@")[0]
}
</span>
<div
className={`px-4 py-2 rounded-2xl max-w-xs overflow-hidden ${
className={`px-4 py-2 rounded max-w-xs overflow-hidden ${
msg.sender_id === user.id ? "bg-indigo-500" : "bg-neutral-700"
}`}
>
Expand Down
5 changes: 4 additions & 1 deletion app/components/landing-page/TopLeaderbord.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Json } from "@/app/supabase-types";

export interface TopMember {
email: string;
total_seconds: number;
categories?: { name: string; total_seconds: number }[];
categories: { name: string; total_seconds: number }[] | null;
user_id: string | null;
}

export default function TopLeaderboard({
Expand Down
5 changes: 3 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import TopLeaderboard, {
} from "./components/landing-page/TopLeaderbord";
import ContributeCard from "./components/landing-page/ContributeCard";
import VibeCoders from "./components/landing-page/VibeCoders";
import { Json } from "./supabase-types";

export default async function Home() {
const supabase = await createClient();
Expand Down Expand Up @@ -44,14 +45,14 @@ export default async function Home() {

const topMembers: TopMember[] = top_members
? top_members.filter(
(u): u is { email: string; total_seconds: number; user_id: string } =>
(u): u is TopMember =>
u.email !== null && u.total_seconds !== null && u.user_id !== null,
)
: [];

const losserMembers: TopMember[] = losser_members
? losser_members.filter(
(u): u is { email: string; total_seconds: number; user_id: string } =>
(u): u is TopMember =>
u.email !== null && u.total_seconds !== null && u.user_id !== null,
)
: [];
Expand Down
4 changes: 4 additions & 0 deletions app/supabase-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,17 @@ export type Database = {
Row: {
created_at: string
id: string
type: string
}
Insert: {
created_at?: string
id?: string
type?: string
}
Update: {
created_at?: string
id?: string
type?: string
}
Relationships: []
}
Expand Down Expand Up @@ -262,6 +265,7 @@ export type Database = {
}
top_user_stats: {
Row: {
categories: Json | null
email: string | null
total_seconds: number | null
user_id: string | null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE conversations ADD COLUMN type text NOT NULL DEFAULT 'private';
6 changes: 6 additions & 0 deletions supabase/migrations/20260323033729_add_global_chat.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Add a global conversation
INSERT INTO conversations (id, type) VALUES ('00000000-0000-0000-0000-000000000001', 'global');

INSERT INTO conversation_participants (conversation_id, user_id, email)
SELECT '00000000-0000-0000-0000-000000000001', id, email
FROM auth.users;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ALTER TABLE conversation_participants
ADD COLUMN type text NOT NULL DEFAULT 'private';

UPDATE conversation_participants
SET type = 'global'
WHERE conversation_id = '00000000-0000-0000-0000-000000000001';
12 changes: 12 additions & 0 deletions supabase/migrations/20260323041543_add_trigger_to_type.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE OR REPLACE FUNCTION sync_conversation_type()
RETURNS TRIGGER AS $$
BEGIN
NEW.type := (SELECT type FROM conversations WHERE id = NEW.conversation_id);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER conversation_type_trigger
BEFORE INSERT ON conversation_participants
FOR EACH ROW
EXECUTE FUNCTION sync_conversation_type();
Loading