Skip to content
Draft
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
20 changes: 13 additions & 7 deletions platforms/pictique/src/lib/fragments/Group/Group.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,33 @@
interface IGroupProps extends HTMLAttributes<HTMLButtonElement> {
avatar: string;
name: string;
text: string;
unread?: boolean;
callback: () => void;
}

const { avatar, name, unread = false, callback, ...restProps }: IGroupProps = $props();
const { avatar, name, unread = false, text, callback, ...restProps }: IGroupProps = $props();

const messageText = $derived(text.length < 80 ? text : `${text.substring(0, 80)}...`);
</script>

<button
{...restProps}
class={cn([
'relative flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-4',
'relative flex w-full cursor-pointer items-center gap-3 rounded-lg py-4',
restProps.class
])}
onclick={callback}
>
<Avatar src={avatar} alt="Group Avatar" size="md" />
<span class="flex w-full items-center justify-between">
<h2 class="text-left font-medium">{name}</h2>
{#if unread}
<span class="h-2 w-2 rounded-full bg-blue-500"></span>
{/if}
<span class="flex w-full flex-col items-start justify-end gap-1">
<span class="flex w-full items-center justify-between">
<h2 class="text-left font-medium">{name}</h2>
{#if unread}
<span class="h-2 w-2 rounded-full bg-blue-500"></span>
{/if}
</span>
<p class="text-start text-black/60">{messageText}</p>
</span>
</button>

Expand Down
177 changes: 93 additions & 84 deletions platforms/pictique/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,111 +1,120 @@
import type { SVGAttributes } from 'svelte/elements';
import type { SVGAttributes } from "svelte/elements";

export interface ISvgProps extends SVGAttributes<SVGElement> {
size?: number | string;
color?: string;
size?: number | string;
color?: string;
}

export type CommentType = {
commentId: string;
name: string;
userImgSrc: string;
comment: string;
isUpVoted: boolean;
isDownVoted: boolean;
upVotes: number;
time: string;
replies: CommentType[];
commentId: string;
name: string;
userImgSrc: string;
comment: string;
isUpVoted: boolean;
isDownVoted: boolean;
upVotes: number;
time: string;
replies: CommentType[];
};

export type PostData = {
createdAt: string | number | Date;
id: string;
avatar: string;
userId: string;
username: string;
imgUris: string[];
caption: string;
time: string;
count: {
likes: number;
comments: number;
};
createdAt: string | number | Date;
id: string;
avatar: string;
userId: string;
username: string;
imgUris: string[];
caption: string;
time: string;
count: {
likes: number;
comments: number;
};
};

export interface Post {
id: string;
text: string;
images: string[];
author: {
id: string;
handle: string;
name: string;
avatarUrl: string;
};
createdAt: string;
likedBy: userProfile[];
comments: {
id: string;
text: string;
author: {
id: string;
handle: string;
name: string;
avatarUrl: string;
};
createdAt: string;
}[];
id: string;
text: string;
images: string[];
author: {
id: string;
handle: string;
name: string;
avatarUrl: string;
};
createdAt: string;
likedBy: userProfile[];
comments: {
id: string;
text: string;
author: {
id: string;
handle: string;
name: string;
avatarUrl: string;
};
createdAt: string;
}[];
}

export type userProfile = {
id: string;
handle: string;
name: string;
description: string;
avatarUrl: string;
totalPosts: number;
followers: number;
following: number;
posts: PostData[];
username: string;
id: string;
handle: string;
name: string;
description: string;
avatarUrl: string;
totalPosts: number;
followers: number;
following: number;
posts: PostData[];
username: string;
};

export type Image = {
url: string;
alt: string;
url: string;
alt: string;
};

export type GroupInfo = {
id: string;
name: string;
avatar: string;
id: string;
name?: string;
avatar: string;
text: string;
unread: boolean;
participants: {
id: string;
name?: string;
handle?: string;
ename?: string;
avatarUrl: string;
}[];
};

export type Chat = {
id: string;
avatar: string;
handle: string;
unread: boolean;
text: string;
participants: {
id: string;
name?: string;
handle?: string;
ename?: string;
avatarUrl: string;
}[];
latestMessage: {
text: string;
isRead: boolean;
};
id: string;
avatar: string;
name?: string;
unread: boolean;
text: string;
participants: {
id: string;
name?: string;
handle?: string;
ename?: string;
avatarUrl: string;
}[];
latestMessage: {
text: string;
isRead: boolean;
};
};

export type MessageType = {
id: string;
avatar: string;
handle: string;
unread: boolean;
text: string;
name: string;
username: string;
id: string;
avatar: string;
handle: string;
unread: boolean;
text: string;
name: string;
username: string;
};
8 changes: 5 additions & 3 deletions platforms/pictique/src/routes/(protected)/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@
<section class="hide-scrollbar h-[100dvh] overflow-y-auto px-4 pb-8 md:px-8 md:pt-8">
<div class="flex flex-col">
<Header
variant={route === `/messages/${idFromParams}` || route.includes('/post')
variant={route === `/messages/${idFromParams}` ||
route.includes('/post') ||
route.includes('/group')
? 'secondary'
: route.includes('profile')
? 'tertiary'
Expand All @@ -127,7 +129,7 @@
</section>
{#if route === '/home' || route === '/messages'}
<aside
class="hide-scrollbar relative hidden h-[100dvh] overflow-y-scroll border border-e-0 border-t-0 border-b-0 border-s-gray-200 px-8 pt-14 md:block"
class="hide-scrollbar relative hidden h-[100dvh] overflow-y-scroll border border-b-0 border-e-0 border-t-0 border-s-gray-200 px-8 pt-14 md:block"
>
{#if route === '/home'}
{#if showComments.value}
Expand Down Expand Up @@ -160,7 +162,7 @@
{/each}
{/if}
<MessageInput
class="sticky start-0 bottom-4 mt-4 w-full px-2"
class="sticky bottom-4 start-0 mt-4 w-full px-2"
variant="comment"
src={profile?.avatarUrl ?? '/images/user.png'}
bind:value={commentValue}
Expand Down
75 changes: 46 additions & 29 deletions platforms/pictique/src/routes/(protected)/messages/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,40 @@
const { data } = await apiClient.get<{ chats: Chat[] }>('/api/chats');
const { data: userData } = await apiClient.get('/api/users');
currentUserId = userData.id;

messages = data.chats.map((c) => {
const members = c.participants.filter((u) => u.id !== userData.id);
const memberNames = members.map((m) => m.name ?? m.handle ?? m.ename);
const avatar =
members.length > 1
? 'https://cdn.jsdelivr.net/npm/[email protected]/icons/people-fill.svg'
: members[0].avatarUrl;
return {
id: c.id,
avatar,
username: c.handle ?? memberNames.join(', '),
unread: c.latestMessage ? c.latestMessage.isRead : false,
text: c.latestMessage?.text ?? 'No message yet',
handle: c.handle ?? memberNames.join(', '),
name: c.handle ?? memberNames.join(', ')
};
});
console.log(data.chats);
messages = data.chats
.filter((c) => c.participants.length <= 2)
.map((c) => {
const members = c.participants.filter((u) => u.id !== userData.id);
const memberNames = members.map((m) => m.name ?? m.handle ?? m.ename);
const avatar =
members.length > 1
? 'https://cdn.jsdelivr.net/npm/[email protected]/icons/people-fill.svg'
: members[0].avatarUrl;
return {
id: c.id,
avatar,
username: c.name ?? memberNames.join(', '),
unread: c.latestMessage ? c.latestMessage.isRead : false,
text: c.latestMessage?.text ?? 'No message yet',
handle: c.name ?? memberNames.join(', '),
name: c.name ?? memberNames.join(', ')
};
});
groups = data.chats
.filter((c) => c.participants.length > 2)
.map((c) => {
console.log(c.participants);
const avatar = '/images/group.png';
return {
id: c.id,
avatar,
unread: c.latestMessage ? c.latestMessage.isRead : false,
text: c.latestMessage?.text ?? 'No message yet',
participants: c.participants,
name: c.name
};
});
}

onMount(async () => {
Expand Down Expand Up @@ -79,14 +95,11 @@
} else {
const groupMembers = allMembers.filter((m) => selectedMembers.includes(m.id));
const groupName = groupMembers.map((m) => m.name ?? m.handle ?? m.ename).join(', ');
groups = [
...groups,
{
id: Math.random().toString(36).slice(2),
name: groupName,
avatar: '/images/group.png'
}
];
await apiClient.post('/api/chats', {
name: groupName,
participantIds: selectedMembers
});
await loadMessages(); // 🛠️ Refresh to include the new group
}
} catch (err) {
console.error('Failed to create chat:', err);
Expand Down Expand Up @@ -128,13 +141,17 @@
{/if}

{#if groups.length > 0}
<h3 class="text-md mt-6 mb-2 font-semibold text-gray-700">Groups</h3>
<h3 class="text-md mb-2 mt-6 font-semibold text-gray-700">Groups</h3>
{#each groups as group}
<Group
name={group.name || 'New Group'}
avatar={group.avatar}
unread={true}
callback={() => goto(`/group/${group.id}`)}
unread={group.unread}
text={group.text}
callback={() => {
heading.set(group.name || 'New Group');
goto(`/group/${group.id}`);
}}
/>
{/each}
{:else if messages.length === 0}
Expand Down