Skip to content

Commit 0144c4d

Browse files
authored
fix: (Pictique) refactor layout structure and fix mobile comments (#656)
* Refactor layout structure to include MainPanel component across various protected routes; enhance user experience with consistent UI elements and improved comment handling in home and messages sections. * fix: update route checks in Header component and adjust layout grid for consistency * fix: message input in comment drawer on mobile * refactor: streamline comment handling and enhance error management in home page
1 parent 7af900a commit 0144c4d

File tree

14 files changed

+636
-579
lines changed

14 files changed

+636
-579
lines changed

platforms/pictique/src/lib/fragments/Drawer/Drawer.svelte

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,15 @@
99
interface IDrawerProps extends HTMLAttributes<HTMLDivElement> {
1010
drawer?: CupertinoPane;
1111
children?: Snippet;
12+
onClose?: () => void;
1213
}
1314
14-
let { drawer = $bindable(), children = undefined, ...restProps }: IDrawerProps = $props();
15+
let {
16+
drawer = $bindable(),
17+
children = undefined,
18+
onClose,
19+
...restProps
20+
}: IDrawerProps = $props();
1521
1622
let drawerElement: HTMLElement;
1723
@@ -46,29 +52,33 @@
4652
bottomClose: true,
4753
buttonDestroy: false,
4854
cssClass: '',
49-
initialBreak: 'middle',
55+
initialBreak: 'top',
56+
breaks: {
57+
top: { enabled: true, height: window.innerHeight * 0.9 },
58+
middle: { enabled: true, height: window.innerHeight * 0.5 }
59+
},
5060
events: {
51-
onBackdropTap: () => dismiss()
61+
onBackdropTap: () => dismiss(),
62+
onWillDismiss: () => onClose?.()
5263
}
5364
});
5465
});
5566
</script>
5667

5768
<div bind:this={drawerElement} {...restProps} {...swipeActions} class={cn(restProps.class)}>
58-
<div class="h-[100%] overflow-y-scroll">
59-
{@render children?.()}
60-
</div>
69+
{@render children?.()}
6170
</div>
6271

6372
<style>
6473
:global(.pane) {
6574
border-top-left-radius: 32px !important;
6675
border-top-right-radius: 32px !important;
6776
padding: 20px !important;
77+
overflow-y: scroll !important;
6878
scrollbar-width: none !important;
6979
-ms-overflow-style: none !important;
70-
::-webkit-scrollbar {
71-
display: none !important;
72-
}
80+
}
81+
:global(.pane)::-webkit-scrollbar {
82+
display: none !important;
7383
}
7484
</style>

platforms/pictique/src/lib/fragments/Header/Header.svelte

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,46 @@
11
<script lang="ts">
2+
import { page } from '$app/state';
23
import { cn } from '$lib/utils';
34
import { ArrowLeft01Icon, ArrowLeft02Icon } from '@hugeicons/core-free-icons';
45
import { HugeiconsIcon } from '@hugeicons/svelte';
56
import type { HTMLAttributes } from 'svelte/elements';
67
7-
interface IHeaderProps extends HTMLAttributes<HTMLElement> {
8-
variant: 'primary' | 'secondary' | 'tertiary';
9-
heading?: string;
10-
isCallBackNeeded?: boolean;
11-
callback?: () => void;
12-
options?: { name: string; handler: () => void }[];
13-
}
8+
const { ...restProps }: HTMLAttributes<HTMLElement> = $props();
149
15-
const { variant, isCallBackNeeded, callback, heading, ...restProps }: IHeaderProps = $props();
10+
let route = $derived(page.url.pathname);
11+
let heading = $state('');
1612
17-
const variantClasses = {
13+
$effect(() => {
14+
if (route.includes('home')) {
15+
heading = 'Feed';
16+
} else if (route.includes('/discover')) {
17+
heading = 'Search';
18+
} else if (route.includes('/post/audience')) {
19+
heading = 'Audience';
20+
} else if (route.includes('/post')) {
21+
heading = 'Upload photo';
22+
} else if (route === '/messages') {
23+
heading = 'Messages';
24+
} else if (route.includes('/settings')) {
25+
heading = 'Settings';
26+
} else if (route.includes('/profile')) {
27+
heading = 'Profile';
28+
}
29+
});
30+
31+
type Variant = 'primary' | 'secondary' | 'tertiary';
32+
33+
let variant = $derived.by((): Variant => {
34+
if (route === `/messages/${page.params.id}` || route.includes('/post')) {
35+
return 'secondary';
36+
}
37+
if (route.includes('profile')) {
38+
return 'tertiary';
39+
}
40+
return 'primary';
41+
});
42+
43+
const variantClasses: Record<Variant, { text: string; background: string }> = {
1844
primary: {
1945
text: 'text-transparent bg-clip-text bg-[image:var(--color-brand-gradient)] py-2',
2046
background: ''
@@ -76,14 +102,6 @@
76102
</h1>
77103
{/if}
78104
</span>
79-
{#if isCallBackNeeded}
80-
<button
81-
class={cn(['cursor-pointer rounded-full p-2 hover:bg-gray-100', classes.background])}
82-
onclick={callback}
83-
aria-label="Callback"
84-
>
85-
</button>
86-
{/if}
87105
</header>
88106

89107
<!--
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<script lang="ts">
2+
import type { HTMLAttributes } from 'svelte/elements';
3+
import Header from '../Header/Header.svelte';
4+
import type { Snippet } from 'svelte';
5+
6+
interface IMainPanelProps extends HTMLAttributes<HTMLDivElement> {
7+
children: Snippet;
8+
RightPanel?: Snippet;
9+
}
10+
11+
let { children, RightPanel }: IMainPanelProps = $props();
12+
</script>
13+
14+
<div class="flex flex-col md:h-dvh md:flex-row">
15+
<section
16+
class="hide-scrollbar min-w-0 flex-1 overflow-y-auto px-4 pb-8 md:h-dvh md:px-8 md:pt-8"
17+
>
18+
<div class="flex flex-col">
19+
<Header />
20+
{@render children()}
21+
</div>
22+
</section>
23+
{#if RightPanel}
24+
<aside
25+
class="hide-scrollbar relative hidden h-dvh w-[30vw] overflow-y-scroll border border-y-0 border-e-0 border-s-gray-200 px-8 pt-12 md:flex md:flex-col"
26+
>
27+
{@render RightPanel?.()}
28+
</aside>
29+
{/if}
30+
</div>

platforms/pictique/src/lib/fragments/RightAside/RightAside.svelte

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@
33
import type { HTMLAttributes } from 'svelte/elements';
44
55
interface IRightAsideProps extends HTMLAttributes<HTMLElement> {
6-
header: Snippet;
7-
asideContent: Snippet;
6+
header?: Snippet;
87
}
9-
let { header, asideContent, ...restProps }: IRightAsideProps = $props();
8+
let { header, children, ...restProps }: IRightAsideProps = $props();
109
</script>
1110

12-
<aside {...restProps} class="hidden border border-y-0 border-s-gray-200 md:block md:pt-13">
13-
<div class="mx-5">
11+
<aside
12+
{...restProps}
13+
class="hide-scrollbar relative hidden h-dvh w-[30vw] overflow-y-scroll border border-y-0 border-e-0 border-s-gray-200 pl-8 md:flex md:flex-col"
14+
>
15+
{#if header}
1416
<h2 class="mb-10 text-lg font-semibold">
1517
{@render header?.()}
1618
</h2>
17-
<div>
18-
{@render asideContent?.()}
19-
</div>
19+
{/if}
20+
<div>
21+
{@render children?.()}
2022
</div>
2123
</aside>

platforms/pictique/src/lib/ui/Avatar/Avatar.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
};
2222
2323
const classes = $derived({
24-
common: cn('rounded-full'),
24+
common: cn('rounded-full shrink-0 aspect-square object-cover'),
2525
size: sizeVariant[size] || sizeVariant.md
2626
});
2727

platforms/pictique/src/routes/(protected)/+layout.svelte

Lines changed: 7 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<script lang="ts">
22
import { goto } from '$app/navigation';
33
import { page } from '$app/state';
4-
import { BottomNav, Comment, Header, MessageInput, SideBar } from '$lib/fragments';
4+
import { BottomNav, SideBar } from '$lib/fragments';
55
import CreatePostModal from '$lib/fragments/CreatePostModal/CreatePostModal.svelte';
6-
import { showComments } from '$lib/store/store.svelte';
7-
import { activePostId, comments, createComment, fetchComments } from '$lib/stores/comments';
86
import { closeDisclaimerModal, isDisclaimerModalOpen } from '$lib/stores/disclaimer';
97
import { isCreatePostModalOpen, openCreatePostModal } from '$lib/stores/posts';
108
import type { userProfile } from '$lib/types';
@@ -13,71 +11,16 @@
1311
import { removeAuthId, removeAuthToken } from '$lib/utils';
1412
import type { AxiosError } from 'axios';
1513
import { onMount } from 'svelte';
16-
import { heading } from '../store';
1714
1815
let { children } = $props();
1916
let ownerId: string | null = $state(null);
2017
let route = $derived(page.url.pathname);
2118
22-
let commentValue: string = $state('');
23-
let commentInput: HTMLInputElement | undefined = $state();
24-
let idFromParams = $state();
25-
let isCommentsLoading = $state(false);
26-
let commentsError = $state<string | null>(null);
2719
let profile = $state<userProfile | null>(null);
2820
let confirmedDisclaimer = $state(false);
2921
30-
const handleSend = async () => {
31-
console.log($activePostId, commentValue);
32-
if (!$activePostId || !commentValue.trim()) return;
33-
34-
try {
35-
await createComment($activePostId, commentValue);
36-
commentValue = '';
37-
} catch (err) {
38-
console.error('Failed to create comment:', err);
39-
}
40-
};
41-
42-
$effect(() => {
43-
idFromParams = page.params.id;
44-
45-
console.log(route);
46-
47-
if (route.includes('home')) {
48-
heading.set('Feed');
49-
} else if (route.includes('discover')) {
50-
heading.set('Search');
51-
} else if (route.includes('/post/audience')) {
52-
heading.set('Audience');
53-
} else if (route.includes('post')) {
54-
heading.set('Upload photo');
55-
} else if (route === '/messages') {
56-
heading.set('Messages');
57-
} else if (route.includes('settings')) {
58-
heading.set('Settings');
59-
} else if (route.includes('profile')) {
60-
heading.set('Profile');
61-
}
62-
});
63-
64-
// Watch for changes in showComments to fetch comments when opened
65-
$effect(() => {
66-
ownerId = getAuthId();
67-
if (showComments.value && activePostId) {
68-
isCommentsLoading = true;
69-
commentsError = null;
70-
fetchComments($activePostId as string)
71-
.catch((err) => {
72-
commentsError = err.message;
73-
})
74-
.finally(() => {
75-
isCommentsLoading = false;
76-
});
77-
}
78-
});
79-
8022
async function fetchProfile() {
23+
ownerId = getAuthId();
8124
try {
8225
if (!getAuthToken()) {
8326
goto('/auth');
@@ -97,83 +40,16 @@
9740
onMount(fetchProfile);
9841
</script>
9942

100-
<main
101-
class={`block h-[100dvh] ${route !== '/home' && route !== '/messages' && route !== '/profile' && !route.includes('settings') && !route.includes('/profile') ? 'grid-cols-[20vw_auto]' : 'grid-cols-[20vw_auto_30vw]'} md:grid`}
102-
>
43+
<main class="block h-dvh grid-cols-[20vw_1fr] md:grid">
10344
<SideBar
10445
profileSrc={profile?.avatarUrl || '/images/user.png'}
10546
handlePost={async () => {
10647
openCreatePostModal();
10748
}}
10849
/>
109-
<section class="hide-scrollbar h-[100dvh] overflow-y-auto px-4 pb-8 md:px-8 md:pt-8">
110-
<div class="flex flex-col">
111-
<Header
112-
variant={route === `/messages/${idFromParams}` || route.includes('/post')
113-
? 'secondary'
114-
: route.includes('profile')
115-
? 'tertiary'
116-
: 'primary'}
117-
heading={$heading}
118-
isCallBackNeeded={route.includes('profile')}
119-
callback={() => alert('Ads')}
120-
options={[
121-
{ name: 'Report', handler: () => alert('report') },
122-
{ name: 'Clear chat', handler: () => alert('clear') }
123-
]}
124-
/>
125-
{@render children()}
126-
</div>
127-
</section>
128-
{#if route === '/home' || route === '/messages'}
129-
<aside
130-
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"
131-
>
132-
{#if route === '/home'}
133-
{#if showComments.value}
134-
<ul class="pb-4">
135-
<h3 class="text-black-600 mb-6 text-center">{$comments.length} Comments</h3>
136-
{#if isCommentsLoading}
137-
<li class="text-center text-gray-500">Loading comments...</li>
138-
{:else if commentsError}
139-
<li class="text-center text-red-500">{commentsError}</li>
140-
{:else}
141-
{#each $comments as comment (comment.id)}
142-
<li class="mb-4">
143-
<Comment
144-
comment={{
145-
userImgSrc: comment.author.avatarUrl,
146-
name: comment.author.name || comment.author.handle,
147-
commentId: comment.id,
148-
comment: comment.text,
149-
isUpVoted: false,
150-
isDownVoted: false,
151-
upVotes: 0,
152-
time: new Date(comment.createdAt).toLocaleDateString(),
153-
replies: []
154-
}}
155-
handleReply={() => {
156-
commentInput?.focus();
157-
}}
158-
/>
159-
</li>
160-
{/each}
161-
{/if}
162-
<MessageInput
163-
class="sticky start-0 bottom-4 mt-4 w-full px-2"
164-
variant="comment"
165-
src={profile?.avatarUrl ?? '/images/user.png'}
166-
bind:value={commentValue}
167-
{handleSend}
168-
bind:input={commentInput}
169-
/>
170-
</ul>
171-
{/if}
172-
{/if}
173-
</aside>
174-
{/if}
50+
{@render children()}
17551

176-
{#if route !== `/messages/${idFromParams}`}
52+
{#if !route.match(/^\/messages\/[^/]+$/)}
17753
<BottomNav class="btm-nav" profileSrc={profile?.avatarUrl ?? ''} />
17854
{/if}
17955
</main>
@@ -197,8 +73,8 @@
19773
core concepts of the W3DS ecosystem.
19874
</p>
19975
<p>
200-
<b>It is not a production-grade platform</b> and may lack full reliability, performance,
201-
and security guarantees.
76+
<b>It is not a production-grade platform</b> and may lack full reliability, performance, and
77+
security guarantees.
20278
</p>
20379
<p>
20480
We <b>strongly recommend</b> that you avoid sharing <b>sensitive or private content</b>,

0 commit comments

Comments
 (0)