Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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: 0 additions & 3 deletions infrastructure/control-panel/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,5 @@ Thumbs.db
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

# Paraglide
src/lib/paraglide

*storybook.log
storybook-static
7 changes: 7 additions & 0 deletions infrastructure/control-panel/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"extends": ["../../biome.json"],
"organizeImports": {
"include": ["src/**/*.ts", "src/**/*.svelte"]
}
}
2 changes: 1 addition & 1 deletion infrastructure/control-panel/src/app.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="%paraglide.lang%">
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
Expand Down
13 changes: 0 additions & 13 deletions infrastructure/control-panel/src/hooks.server.ts

This file was deleted.

3 changes: 0 additions & 3 deletions infrastructure/control-panel/src/hooks.ts

This file was deleted.

This file was deleted.

This file was deleted.

12 changes: 2 additions & 10 deletions infrastructure/control-panel/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import { paraglideVitePlugin } from '@inlang/paraglide-js';
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';

export default defineConfig({
plugins: [
tailwindcss(),
sveltekit(),
paraglideVitePlugin({
project: './project.inlang',
outdir: './src/lib/paraglide'
})
]
plugins: [tailwindcss(), sveltekit()]
});
14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,19 @@
},
"pnpm": {
"onlyBuiltDependencies": [
"esbuild"
"@biomejs/biome",
"@firebase/util",
"@tailwindcss/oxide",
"bufferutil",
"core-js",
"cpu-features",
"esbuild",
"msw",
"protobufjs",
"sharp",
"ssh2",
"svelte-preprocess",
"unrs-resolver"
]
}
}
3 changes: 2 additions & 1 deletion platforms/blabsy/src/components/chat/chat-window.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ export function ChatWindow(): JSX.Element {
void Promise.all(
unreadMessages.map((message) => markAsRead(message.id))
);
}});
}
}, [currentChat, messages, user, markAsRead]);

const handleSubmit = async (e: React.FormEvent): Promise<void> => {
e.preventDefault();
Expand Down
36 changes: 32 additions & 4 deletions platforms/pictique/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,14 @@ export interface Post {
}

export type userProfile = {
userId: string;
username: string;
id: string;
handle: string;
name: string;
description: string;
avatarUrl: string;
Comment on lines 60 to 65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Duplicate identity fields create ambiguity

userProfile now exposes both handle and username. Unless you have a clear “old vs. new” migration story, keeping both almost guarantees accidental misuse and brittle mapping code.

• Pick one canonical field (ideally handle) and deprecate/remove the other, or
• Mark the legacy one with a JSDoc @deprecated tag and schedule removal.

Also applies to: 70-70


🏁 Script executed:

#!/bin/bash
# Check the current state of userProfile type definition to see all fields
cat platforms/pictique/src/lib/types.ts | grep -A 20 "export type userProfile"

Length of output: 407


🏁 Script executed:

#!/usr/bin/env bash
# Search for .username usage across the codebase
rg "\.username\b" -n

# Search for .handle usage across the codebase
rg "\.handle\b" -n

Length of output: 7490


Deprecate legacy username in favor of handle

The userProfile type and UI code currently expose both username and handle, leading to ambiguity and the risk of inconsistent usage:

• platforms/pictique/src/lib/types.ts – userProfile defines both handle and username
• platforms/pictique/src/lib/fragments/Profile/Profile.svelte – uses profileData.username (alt/text) and profileData.handle (heading)
• platforms/pictique/src/lib/fragments/PostModal/PostModal.svelte – uses comment.author.handle
• platforms/pictique/src/routes/(protected)/profile/[id]/+page.svelte – references both profile?.username and profile?.handle
• platforms/pictique/src/routes/(protected)/messages/+page.svelte – renders message.username and message.handle in different contexts
• platforms/pictique/src/routes/(protected)/home/+page.svelte & +layout.svelte – intermix post.author.handle and legacy post.author.username

Please pick one canonical identity field (preferably handle), annotate the legacy field with a JSDoc @deprecated tag, and schedule its removal once all usages have been updated.

🤖 Prompt for AI Agents
In platforms/pictique/src/lib/types.ts around lines 60 to 65, the userProfile
type defines both handle and username, causing ambiguity. Mark the username
field with a JSDoc @deprecated annotation to indicate it is legacy and should
not be used. Keep handle as the canonical identity field. Plan to remove
username after updating all code references to use handle exclusively.

totalPosts: number;
followers: number;
following: number;
userBio: string;
posts: PostData[];
};

Expand All @@ -77,4 +78,31 @@ export type GroupInfo = {
id: string;
name: string;
avatar: 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;
};
};

export type MessageType = {
id: string;
avatar: string;
handle: string;
unread: boolean;
text: string;
};
71 changes: 48 additions & 23 deletions platforms/pictique/src/routes/(protected)/group/[id]/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts">
import { onMount } from 'svelte';
import { ChatMessage, MessageInput } from '$lib/fragments';
import { Avatar, Button, Input, Label } from '$lib/ui';
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { ChatMessage, MessageInput } from '$lib/fragments';
import Settings from '$lib/icons/Settings.svelte';
import { Avatar, Button, Input, Label } from '$lib/ui';
import { clickOutside } from '$lib/utils';
import { Pen01FreeIcons } from '@hugeicons/core-free-icons';
import {HugeiconsIcon} from "@hugeicons/svelte"
import { HugeiconsIcon } from '@hugeicons/svelte';
import { onMount } from 'svelte';

let messagesContainer: HTMLDivElement;
let messageValue = $state('');
Expand All @@ -20,9 +20,19 @@
avatar: 'https://i.pravatar.cc/150?img=15',
description: 'Discuss all design-related tasks and updates here.',
members: [
{ id: 'user-1', name: 'Alice', avatar: 'https://i.pravatar.cc/150?img=1', role: 'owner' },
{
id: 'user-1',
name: 'Alice',
avatar: 'https://i.pravatar.cc/150?img=1',
role: 'owner'
},
{ id: 'user-2', name: 'Bob', avatar: 'https://i.pravatar.cc/150?img=2', role: 'admin' },
{ id: 'user-3', name: 'Charlie', avatar: 'https://i.pravatar.cc/150?img=3', role: 'member' }
{
id: 'user-3',
name: 'Charlie',
avatar: 'https://i.pravatar.cc/150?img=3',
role: 'member'
}
]
});

Expand Down Expand Up @@ -53,8 +63,8 @@
setTimeout(scrollToBottom, 0);
});

function handleSend() {
if (!messageValue.trim()) return;
async function handleSend() {
if (!messageValue.trim()) return Promise.resolve();
messages = [
...messages,
{
Expand All @@ -75,7 +85,7 @@
let groupImageDataUrl = $state(group.avatar);
let groupImageFiles = $state<FileList | undefined>();

$effect(()=> {
$effect(() => {
if (groupImageFiles?.[0]) {
const file = groupImageFiles[0];
const reader = new FileReader();
Expand All @@ -86,8 +96,8 @@
};
reader.readAsDataURL(file);
}
})
});

function saveGroupInfo() {
group.name = groupName;
group.description = groupDescription;
Expand All @@ -99,7 +109,9 @@
const canEdit = currentUser?.role === 'admin' || currentUser?.role === 'owner';
</script>

<section class="flex flex-col md:flex-row items-center justify-between gap-4 px-2 md:px-4 py-3 border-b border-gray-200">
<section
class="flex flex-col items-center justify-between gap-4 border-b border-gray-200 px-2 py-3 md:flex-row md:px-4"
>
<div class="flex items-center gap-4">
<Avatar src={group.avatar} />
<div>
Expand All @@ -120,15 +132,15 @@
</Button>
<button
onclick={() => (openEditDialog = true)}
class="border border-brand-burnt-orange-900 rounded-full p-2"
class="border-brand-burnt-orange-900 rounded-full border p-2"
>
<Settings size="24px" color="var(--color-brand-burnt-orange)" />
</button>
</div>
</section>

<section class="chat relative px-0">
<div class="h-[calc(100vh-300px)] mt-4 overflow-auto" bind:this={messagesContainer}>
<div class="mt-4 h-[calc(100vh-300px)] overflow-auto" bind:this={messagesContainer}>
{#each messages as msg (msg.id)}
<ChatMessage
isOwn={msg.isOwn}
Expand All @@ -152,14 +164,14 @@
open={openEditDialog}
use:clickOutside={() => (openEditDialog = false)}
onclose={() => (openEditDialog = false)}
class="w-[90vw] md:max-w-[30vw] z-50 absolute start-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] p-4 border border-gray-400 rounded-3xl bg-white shadow-xl"
class="absolute start-[50%] top-[50%] z-50 w-[90vw] translate-x-[-50%] translate-y-[-50%] rounded-3xl border border-gray-400 bg-white p-4 shadow-xl md:max-w-[30vw]"
>
<div class="flex flex-col gap-6">
<div class="relative w-[96px] h-[96px] self-center">
<div class="relative h-[96px] w-[96px] self-center">
<img
src={groupImageDataUrl || '/images/avatar-placeholder.png'}
alt="Group Avatar"
class="w-full h-full object-cover rounded-full border border-gray-300"
class="h-full w-full rounded-full border border-gray-300 object-cover"
/>
{#if canEdit}
<input
Expand All @@ -174,8 +186,11 @@
}
}}
/>
<label for="group-avatar-input" class="absolute bottom-0 right-0 bg-brand-burnt-orange border border-brand-burnt-orange rounded-full p-1 shadow cursor-pointer">
<HugeiconsIcon icon={Pen01FreeIcons} color="white"/>
<label
for="group-avatar-input"
class="bg-brand-burnt-orange border-brand-burnt-orange absolute right-0 bottom-0 cursor-pointer rounded-full border p-1 shadow"
>
<HugeiconsIcon icon={Pen01FreeIcons} color="white" />
</label>
{/if}
</div>
Expand All @@ -193,8 +208,13 @@
<Label>Description</Label>
{#if canEdit}
<!-- svelte-ignore element_invalid_self_closing_tag -->
<textarea rows="2"
maxlength="260" placeholder="Edit group description" class="w-full bg-grey py-3.5 px-6 text-[15px] text-black-800 font-geist font-normal placeholder:text-black-600 rounded-4xl outline-0 border border-transparent invalid:border-red invalid:text-red focus:invalid:text-black-800 focus:invalid:border-transparent" bind:value={groupDescription} />
<textarea
rows="2"
maxlength="260"
placeholder="Edit group description"
class="bg-grey text-black-800 font-geist placeholder:text-black-600 invalid:border-red invalid:text-red focus:invalid:text-black-800 w-full rounded-4xl border border-transparent px-6 py-3.5 text-[15px] font-normal outline-0 focus:invalid:border-transparent"
bind:value={groupDescription}
/>
{:else}
<p class="text-gray-700">{group.description}</p>
{/if}
Expand All @@ -203,11 +223,16 @@
<hr class="text-grey" />

<div class="flex items-center gap-2">
<Button size="sm" variant="primary" callback={() => {(openEditDialog = false)}}>Cancel</Button>
<Button
size="sm"
variant="primary"
callback={() => {
openEditDialog = false;
}}>Cancel</Button
>
{#if canEdit}
<Button size="sm" variant="secondary" callback={saveGroupInfo}>Save Changes</Button>
{/if}
</div>
</div>
</dialog>

Loading
Loading