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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=
NEXT_PUBLIC_HCAPTCHA_SITE_KEY=

SENTRY_AUTH_TOKEN=
SENTRY_ORG=
SENTRY_PROJECT=
SENTRY_DNS=
38 changes: 26 additions & 12 deletions app/components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export default function Chat({ user }: { user: User }) {
const [allUsers, setAllUsers] = useState<ChatUser[]>([]);
const channelRef = useRef<RealtimeChannel>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const [creatingConversation, setCreatingConversation] = useState(false);
const bottomRef = useRef<HTMLDivElement | null>(null);
const creatingRef = useRef(false);

useEffect(() => {
if (textareaRef.current) {
Expand Down Expand Up @@ -124,6 +124,16 @@ export default function Chat({ user }: { user: User }) {
.then(({ data }) => {
if (!data || data.length === 0) return;
const convo = data[0];
// Double check the user is part of the conversation (should always be true)
if (
!convo.users.some(
(u: { user_id: string }) => u.user_id === user.id,
)
)
return;
// Check if we already have this conversation in state
if (conversations.some((c) => c.id === convo.id)) return;

setConversations((prev) => [
...prev,
{
Expand All @@ -141,7 +151,7 @@ export default function Chat({ user }: { user: User }) {
return () => {
channel.unsubscribe();
};
}, [user.id]);
}, [user.id, conversations]);

useEffect(() => {
if (!conversationId) return;
Expand Down Expand Up @@ -223,9 +233,9 @@ export default function Chat({ user }: { user: User }) {
}, [showModal, user.id]);

const createConversation = async (otherUser: ChatUser) => {
if (creatingConversation) return;
if (creatingRef.current) return;
creatingRef.current = true;

setCreatingConversation(true);
const existing = conversations.find((conv) =>
conv.users.some((u) => u.id === otherUser.user_id),
);
Expand Down Expand Up @@ -274,8 +284,9 @@ export default function Chat({ user }: { user: User }) {
],
},
]);
setCreatingConversation(false);

setShowModal(false);
creatingRef.current = false;
};

const sendMessage = async () => {
Expand All @@ -296,22 +307,25 @@ export default function Chat({ user }: { user: User }) {

return (
<div className="flex flex-col h-screen">
<div className="p-4 flex gap-4 overflow-x-auto border-b border-neutral-700">
<div className="px-3 pt-3 flex border-neutral-700">
<button
onClick={() => setShowModal(true)}
className="flex flex-col items-center min-w-15"
>
<div className="w-12 h-12 rounded-full bg-indigo-500 flex items-center justify-center">
<div className="w-10 h-10 rounded-full bg-indigo-500 flex items-center justify-center">
<FontAwesomeIcon icon={faPlus} className="text-white" />
</div>
<span className="text-xs mt-1">New</span>
</button>

<Conversations
conversations={conversations}
user={user}
setConversationId={setConversationId}
/>
<div className="flex-1 flex gap-4 overflow-x-auto">
<Conversations
conversations={conversations}
user={user}
conversationId={conversationId}
setConversationId={setConversationId}
/>
</div>
</div>

{conversationId ? (
Expand Down
24 changes: 18 additions & 6 deletions app/components/chat/Conversations.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
"use client";
import { User } from "@supabase/supabase-js";
import { Conversation } from "../Chat";

export default function Conversations({
conversations,
user,
conversationId,
setConversationId,
}: {
conversations: Conversation[];
user: User;
conversationId: string | null;
setConversationId: (id: string) => void;
}) {
return (
<>
{conversations.map((conv, idx) => {
const otherUser = conv.users.find((u) => u.id !== user.id);
const isActive = conv.id === conversationId; // check active

return (
<div
key={idx}
onClick={() => setConversationId(conv.id)}
className="flex flex-col items-center min-w-15 cursor-pointer"
>
<div className="flex justify-center items-center w-12 h-12 rounded-full bg-neutral-600">
{otherUser?.email[0].toUpperCase()}
<div
className={`
flex justify-center items-center w-10 h-10 rounded-full border border-white/10
${isActive ? "bg-indigo-500 text-white" : "bg-white/5 text-gray-300"}
`}
>
{otherUser?.email[0]?.toUpperCase()}
</div>
<span className="text-xs mt-1">
{otherUser?.email.split("@")[0]}
<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;
})()}
</span>
</div>
);
})}
</>
);
}

51 changes: 44 additions & 7 deletions app/components/chat/Messages.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
"use client";

import { User } from "@supabase/supabase-js";
import ReactMarkdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { atomDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { Conversation, Message } from "../Chat";
import { timeAgo } from "@/app/utils/time";
import { useEffect, useState } from "react";

export default function Messages({
messages,
Expand All @@ -16,9 +19,42 @@ export default function Messages({
conversations: Conversation[];
bottomRef: React.RefObject<HTMLDivElement | null>;
}) {
const [showScrollBtn, setShowScrollBtn] = useState(false);

useEffect(() => {
const container = document.getElementById("chat-container");

if (!container) return;

const handleScroll = () => {
const isNearBottom =
container.scrollHeight - container.scrollTop - container.clientHeight <
100;

setShowScrollBtn(!isNearBottom);
};

container.addEventListener("scroll", handleScroll);
return () => container.removeEventListener("scroll", handleScroll);
}, []);

return (
<>
<div className="flex-1 overflow-y-auto p-4 space-y-3">
<div className="flex-1 overflow-y-auto p-4 space-y-3" id="chat-container">
{showScrollBtn && (
<button
onClick={() =>
bottomRef.current?.scrollIntoView({ behavior: "smooth" })
}
className="fixed right-4 top-1/2 -translate-y-1/2 z-50
bg-white/10 hover:bg-white/20
backdrop-blur-md border border-white/10
text-white rounded-full p-3 shadow-lg transition"
>
</button>
)}

{messages.length === 0 && (
<div className="text-gray-500 text-sm italic text-center mt-10">
No messages yet. Start the conversation!
Expand All @@ -41,7 +77,7 @@ export default function Messages({
)}
<div>
<div
className={`px-4 py-2 rounded-2xl max-w-xs ${
className={`px-4 py-2 rounded-2xl max-w-xs overflow-hidden ${
msg.sender_id === user.id ? "bg-indigo-500" : "bg-neutral-700"
}`}
>
Expand All @@ -53,11 +89,12 @@ export default function Messages({

return (
<>
{ }
{}

<SyntaxHighlighter

style={atomDark as { [key: string]: React.CSSProperties }}
style={
atomDark as { [key: string]: React.CSSProperties }
}
language={match ? match[1] : "text"}
PreTag="pre"
className="rounded-md text-sm"
Expand All @@ -74,7 +111,7 @@ export default function Messages({
</ReactMarkdown>
</div>

<div className="text-muted text-sm">
<div className="text-muted text-xs mt-1">
{timeAgo(msg.created_at)}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/components/landing-page/TopLeaderbord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function TopLeaderboard({
Celebrating the most dedicated coders in our community.
</p>

<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{top_members.map(
(
member: { email: string; total_seconds: number },
Expand Down
2 changes: 1 addition & 1 deletion instrumentation-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import * as Sentry from "@sentry/nextjs";

Sentry.init({
dsn: "https://d1a39ca51d4a6f8ce8f14535febe5d6b@o4508073369862144.ingest.de.sentry.io/4511078474711120",
dsn: process.env.SENTRY_DNS,

// Enable sending user PII (Personally Identifiable Information)
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
Expand Down
4 changes: 2 additions & 2 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ export default withSentryConfig(nextConfig, {
// For all available options, see:
// https://www.npmjs.com/package/@sentry/webpack-plugin#options

org: "melvin-jones-repol",
org: process.env.SENTRY_ORG,

project: "devpulse",
project: process.env.SENTRY_PROJECT,

// Only print logs for uploading source maps in CI
silent: !process.env.CI,
Expand Down
Loading
Loading