Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8d266ac
Add backend API for recursive posts
scazan Jul 23, 2025
3e5eed2
Adding in Comment Modal with component styles
scazan Jul 23, 2025
33d6067
Rename to DiscussionModal
scazan Jul 23, 2025
8a83ed1
Update styles on DiscussionModal
scazan Jul 23, 2025
caf0426
Allow for passing of org
scazan Jul 23, 2025
64a0d49
Add CommentButton
scazan Jul 23, 2025
b00f6a1
CommentButton export
scazan Jul 23, 2025
60e8fb0
Add CommentButton to PostFeed
scazan Jul 23, 2025
f7d88e4
Styles on CommentButton for hover, pressed, etc.
scazan Jul 23, 2025
9ba82b3
comment counts
scazan Jul 23, 2025
786edae
Update comment button style
scazan Jul 23, 2025
48fabfc
Update IconButton component
scazan Jul 23, 2025
ac9ca22
feature flag comments
scazan Jul 23, 2025
2f4929e
fix types
scazan Jul 23, 2025
eae864e
Remove any type on table!
scazan Jul 24, 2025
3b4ef96
Merge branch 'dev' into feature/comments-as-posts
scazan Jul 24, 2025
4e584f0
Merge branch 'dev' into feature/comments-as-posts
scazan Jul 24, 2025
06cbceb
Merge branch 'dev' into feature/comments-as-posts
scazan Jul 24, 2025
1621e3b
update prettier formatting
scazan Jul 24, 2025
e27ac04
update types
scazan Jul 24, 2025
73c5876
update typings
scazan Jul 24, 2025
775ab56
fixup typings on DiscussionModal
scazan Jul 24, 2025
ae70a85
refactor PostFeed for better composition
scazan Jul 24, 2025
585bcba
Refactor for more composability
scazan Jul 24, 2025
1e5b939
Allow for better customizing of PostUpdate
scazan Jul 24, 2025
0b1ea5e
add index
scazan Jul 24, 2025
d5e1661
remove any
scazan Jul 24, 2025
29b0b06
simplifying logic
scazan Jul 24, 2025
61a1a9b
add Feed directory
scazan Jul 24, 2025
effb7bf
comment style tweaks
scazan Jul 24, 2025
31ccd93
remove any
scazan Jul 24, 2025
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
1 change: 1 addition & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"bracketSameLine": false,
"singleQuote": true,
"tabWidth": 2,
"semi": true,
Expand Down
152 changes: 152 additions & 0 deletions apps/app/src/components/DiscussionModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
'use client';

import { useUser } from '@/utils/UserProvider';
import { trpc } from '@op/api/client';
import type { PostToOrganization } from '@op/api/encoders';
import { Button } from '@op/ui/Button';
import { Modal, ModalFooter, ModalHeader } from '@op/ui/Modal';
import { Surface } from '@op/ui/Surface';
import { LuX } from 'react-icons/lu';

import { useTranslations } from '@/lib/i18n';

import { PostFeed, PostItem, usePostFeedActions } from '../PostFeed';
import { PostUpdate } from '../PostUpdate';

export function DiscussionModal({
postToOrg,
isOpen,
onClose,
}: {
postToOrg: PostToOrganization;
isOpen: boolean;
onClose: () => void;
}) {
const utils = trpc.useUtils();
const { user } = useUser();
const t = useTranslations();
const { post, organization } = postToOrg;

const { handleReactionClick, handleCommentClick } = usePostFeedActions({
slug: organization?.profile?.slug,
});

const { data: commentsData, isLoading } = trpc.posts.getPosts.useQuery(
{
parentPostId: post.id, // Get comments (child posts) of this post
limit: 50,
offset: 0,
includeChildren: false,
},
{ enabled: isOpen },
);

const handleCommentSuccess = () => {
utils.posts.getPosts.invalidate({
parentPostId: post.id, // Invalidate comments for this post
});
};

const sourcePostProfile = post.profile;

// Get the post author's name for the header
const authorName = sourcePostProfile?.name || 'Unknown';

// Transform comments data to match PostFeeds expected PostToOrganizaion format
const comments =
commentsData?.map((comment) => ({
createdAt: comment.createdAt,
updatedAt: comment.updatedAt,
deletedAt: null,
postId: comment.id,
organizationId: '', // Not needed for comments
post: comment,
organization: null, // Comments don't need organization context in the modal
})) || [];

return (
<Modal
isOpen={isOpen}
onOpenChange={onClose}
isDismissable
className="h-svh max-h-none w-screen max-w-none overflow-y-auto rounded-none text-left sm:h-auto sm:max-h-[75vh] sm:w-[36rem] sm:max-w-[36rem] sm:rounded-md"
>
<ModalHeader className="flex items-center justify-between">
{/* Desktop header */}
<div className="hidden sm:flex sm:w-full sm:items-center sm:justify-between">
{organization?.profile.name}'s Post
<LuX className="size-6 cursor-pointer stroke-1" onClick={onClose} />
</div>

{/* Mobile header */}
<div className="flex w-full items-center justify-between sm:hidden">
<Button
unstyled
className="font-sans text-base text-primary-teal"
onPress={onClose}
>
Close
</Button>
<h2 className="text-title-sm">{authorName}'s Post</h2>
<div className="w-12" /> {/* Spacer for center alignment */}
</div>
</ModalHeader>

<div className="flex flex-col gap-4">
<div className="max-h-96 flex-1 overflow-y-auto px-4 pt-6">
{/* Original Post Display */}
<PostFeed className="border-none">
<PostItem
postToOrg={postToOrg}
user={user}
withLinks={false}
onReactionClick={handleReactionClick}
/>
<hr className="bg-neutral-gray1" />
</PostFeed>
{/* Comments Display */}
{isLoading ? (
<div className="py-8 text-center text-gray-500">
Loading discussion...
</div>
) : comments.length > 0 ? (
<PostFeed className="border-none">
{comments.map((comment, i) => (
<>
<PostItem
key={i}
postToOrg={comment}
user={user}
withLinks={false}
onReactionClick={handleReactionClick}
onCommentClick={handleCommentClick}
className="sm:px-0"
/>
{comments.length !== i + 1 && (
<hr className="bg-neutral-gray1" />
)}
</>
))}
</PostFeed>
) : (
<div className="py-8 text-center text-gray-500">
No comments yet. Be the first to comment!
</div>
)}
</div>

{/* Comment Input using PostUpdate */}
<ModalFooter className="hidden px-4 sm:flex">
<Surface className="w-full border-0 p-0 pt-5 sm:border sm:p-4">
<PostUpdate
parentPostId={post.id}
placeholder={`Comment${user?.currentProfile?.name ? ` as ${user?.currentProfile?.name}` : ''}...`}
onSuccess={handleCommentSuccess}
label={t('Comment')}
/>
</Surface>
</ModalFooter>
</div>
</Modal>
);
}
75 changes: 75 additions & 0 deletions apps/app/src/components/Feed/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { cn } from '@op/ui/utils';
import { ReactNode } from 'react';

export const FeedItem = ({
children,
className,
}: {
children: ReactNode;
className?: string;
}) => {
return <div className={cn('flex gap-2', className)}>{children}</div>;
};

export const FeedContent = ({
children,
className,
}: {
children: ReactNode;
className?: string;
}) => {
return (
<div
className={cn(
'flex w-full flex-col gap-2 leading-6 [&>.mediaItem:first-child]:mt-2',
className,
)}
style={{ overflowWrap: 'anywhere' }}
>
{children}
</div>
);
};

export const FeedHeader = ({
children,
className,
}: {
children: ReactNode;
className?: string;
}) => {
return (
<span className={cn('flex items-center gap-2 align-baseline', className)}>
{children}
</span>
);
};

export const FeedAvatar = ({ children }: { children?: ReactNode }) => {
return (
<div className="shadown relative w-8 min-w-8 overflow-hidden">
{children}
</div>
);
};

export const FeedMain = ({
children,
className,
...props
}: {
children: ReactNode;
className?: string;
} & React.HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={cn(
'flex w-full flex-col items-start justify-start gap-2 overflow-hidden',
className,
)}
{...props}
>
{children}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export const MatchingOrganizationsForm = ({
className="hidden"
/>
<OrganizationAvatar
organization={org}
profile={org.profile}
withLink={false}
className="size-12"
/>
Expand Down
16 changes: 4 additions & 12 deletions apps/app/src/components/OrganizationAvatar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import { getPublicUrl } from '@/utils';
import { RouterOutput } from '@op/api/client';
import { Profile } from '@op/api/encoders';
import { Avatar, AvatarSkeleton } from '@op/ui/Avatar';
import { cn } from '@op/ui/utils';
import Image from 'next/image';

import { Link } from '@/lib/i18n';

type relationshipOrganization =
| RouterOutput['organization']['listRelationships']['organizations'][number]
| RouterOutput['organization']['list']['items'][number]
| RouterOutput['organization']['listPosts']['items'][number]['organization'];

export const OrganizationAvatar = ({
organization,
profile,
withLink = true,
className,
}: {
organization?: relationshipOrganization;
profile?: Profile;
withLink?: boolean;
className?: string;
}) => {
if (!organization) {
if (!profile) {
return null;
}

// TODO: fix type resolution in drizzle.
const profile = 'profile' in organization ? organization.profile : null;

const name = profile?.name ?? '';
const avatarImage = profile?.avatarImage;
const slug = profile?.slug;
Expand Down
8 changes: 4 additions & 4 deletions apps/app/src/components/OrganizationList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const OrganizationList = ({
{organizations?.map((org) => {
return (
<div key={org.id} className="flex items-center gap-2">
<OrganizationAvatar organization={org} className="size-8" />
<OrganizationAvatar profile={org.profile} className="size-8" />

<div className="flex min-w-0 flex-col text-sm sm:text-base">
<Link
Expand Down Expand Up @@ -128,7 +128,7 @@ export const OrganizationCardList = ({
>
<div className="flex-shrink-0">
<OrganizationAvatar
organization={relationshipOrg}
profile={relationshipOrg.profile}
className="size-20"
/>
</div>
Expand All @@ -145,7 +145,7 @@ export const OrganizationCardList = ({

<div className="line-clamp-3 text-neutral-charcoal">
{relationshipOrg.profile.bio &&
relationshipOrg.profile.bio.length > 200
relationshipOrg.profile.bio.length > 200
? `${relationshipOrg.profile.bio.slice(0, 200)}...`
: relationshipOrg.profile.bio}
</div>
Expand Down Expand Up @@ -190,7 +190,7 @@ export const OrganizationSummaryList = ({
src={
getPublicUrl(
org.profile.avatarImage?.name ??
org.avatarImage?.name,
org.avatarImage?.name,
) ?? ''
}
alt={org.profile.name ?? ''}
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/components/PendingRelationships/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const PendingRelationshipsSuspense = ({ slug }: { slug: string }) => {
className={`flex flex-col justify-between gap-6 border-t p-6 transition-colors sm:flex-row sm:items-center sm:gap-2 ${isAccepted ? 'bg-primary-tealWhite' : ''}`}
>
<div className="flex items-center gap-3">
<OrganizationAvatar organization={org} />
<OrganizationAvatar profile={org.profile} />
<div className="flex h-full flex-col">
<span className="font-bold">
{org.profile.name}
Expand Down
Loading
Loading