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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const ActiveDecisionsNotificationsSuspense = () => {

const [{ items: decisions }] =
trpc.decision.listDecisionProfiles.useSuspenseQuery({
status: ProcessStatus.PUBLISHED,
status: [ProcessStatus.PUBLISHED],
limit: 10,
});

Expand Down
36 changes: 32 additions & 4 deletions apps/app/src/components/Profile/ProfileContent/DecisionsTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use client';

import { useUser } from '@/utils/UserProvider';
import { trpc } from '@op/api/client';
import { ProcessStatus, VISIBLE_DECISION_STATUSES } from '@op/api/encoders';
import { Tab, TabPanel } from '@op/ui/Tabs';
import { cn } from '@op/ui/utils';
import { ReactNode } from 'react';
Expand All @@ -14,11 +16,37 @@ import { MembersList } from './MembersList';
export const DecisionsTab = ({ profileId }: { profileId: string }) => {
const t = useTranslations();
const access = useUser();
const permission = access.getPermissionsForProfile(profileId);
const canReadDecisions =
access.getPermissionsForProfile(profileId).decisions.read;

return permission.decisions.read ? (
<Tab id="decisions">{t('Decisions')}</Tab>
) : null;
const decisionProfiles = trpc.decision.listDecisionProfiles.useQuery({
stewardProfileId: profileId,
status: VISIBLE_DECISION_STATUSES,
});

const legacyInstances = trpc.decision.listInstances.useQuery(
{ stewardProfileId: profileId, limit: 1, offset: 0 },
{ retry: false, enabled: canReadDecisions },
);

const hasDecisionProfiles = (decisionProfiles.data?.items?.length ?? 0) > 0;
const hasLegacyInstances = (legacyInstances.data?.instances?.length ?? 0) > 0;
const hasPublishedDecisions = decisionProfiles.data?.items?.some(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Might need to restructure this a bit on the backend since ordering needs to push published to the top. Followup PR for that.

(item) => item.processInstance.status === ProcessStatus.PUBLISHED,
);

if (!hasDecisionProfiles && !hasLegacyInstances) {
return null;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Don't show the tab

}

return (
<Tab id="decisions">
{t('Decisions')}
{hasPublishedDecisions && (
<span className="ml-1.5 inline-block size-1 rounded-full bg-functional-green" />
)}
</Tab>
);
};

export const DecisionsTabPanel = ({
Expand Down
21 changes: 5 additions & 16 deletions apps/app/src/components/Profile/ProfileContent/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';

import { useUser } from '@/utils/UserProvider';
import { checkModuleEnabled } from '@/utils/modules';
import { trpc } from '@op/api/client';
import { type Organization, ProcessStatus } from '@op/api/encoders';
import { formatToUrl } from '@op/common/validation';
Expand Down Expand Up @@ -255,8 +254,8 @@ const ProfileDecisions = ({ profileId }: { profileId: string }) => {

const [data] = trpc.decision.listDecisionProfiles.useSuspenseQuery({
limit: 3,
ownerProfileId: profileId,
status: ProcessStatus.PUBLISHED,
stewardProfileId: profileId,
status: [ProcessStatus.PUBLISHED],
});

if (!data.items[0]) {
Expand Down Expand Up @@ -393,10 +392,6 @@ export const ProfileTabsMobile = ({
}) => {
const t = useTranslations();
const isIndividual = profile.orgType === null || profile.orgType === '';
const decisionsEnabled = checkModuleEnabled(
profile.profile.modules,
'decisions',
);

// Determine valid tabs and default tab based on profile type
const validTabs = [
Expand Down Expand Up @@ -425,9 +420,7 @@ export const ProfileTabsMobile = ({
<Tab id="updates">{t('Updates')}</Tab>
<FollowersTab />
<MembersTab profileId={profile.profile.id} />
{decisionsEnabled && (
<DecisionsTab profileId={profile.profile.id} />
)}
<DecisionsTab profileId={profile.profile.id} />
</>
) : (
<>
Expand Down Expand Up @@ -490,14 +483,10 @@ export const ProfileTabsMobile = ({
{!isIndividual && (
<>
<FollowersTabPanel>{followersContent}</FollowersTabPanel>
{decisionsEnabled && (
<MembersTabPanel profileId={profile.profile.id} />
)}
<MembersTabPanel profileId={profile.profile.id} />
</>
)}
{decisionsEnabled && (
<DecisionsTabPanel>{decisionsContent}</DecisionsTabPanel>
)}
<DecisionsTabPanel>{decisionsContent}</DecisionsTabPanel>
</ProfileTabsWithQuery>
);
};
138 changes: 102 additions & 36 deletions apps/app/src/components/Profile/ProfileDecisions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useUser } from '@/utils/UserProvider';
import { trpc } from '@op/api/client';
import { VISIBLE_DECISION_STATUSES } from '@op/api/encoders';
import { getTextPreview } from '@op/core';
import { Button } from '@op/ui/Button';
import { DialogTrigger } from '@op/ui/Dialog';
Expand All @@ -13,10 +14,32 @@ import { LuLeaf } from 'react-icons/lu';

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

import ErrorBoundary from '@/components/ErrorBoundary';
import { DecisionListItem } from '@/components/decisions/DecisionListItem';

import type { SchemaType } from '../CreateDecisionProcessModal/schemas/schemaLoader';
import { EditDecisionProcessModal } from '../EditDecisionProcessModal';

const DecisionProcessList = ({
const DecisionProfilesList = ({ profileId }: { profileId: string }) => {
const [data] = trpc.decision.listDecisionProfiles.useSuspenseQuery({
stewardProfileId: profileId,
status: VISIBLE_DECISION_STATUSES,
});

if (!data.items.length) {
return null;
}

return (
<div className="flex flex-col">
{data.items.map((item) => (
<DecisionListItem key={item.id} item={item} />
))}
</div>
);
};

const LegacyDecisionProcessList = ({
profileId,
schema = 'simple',
}: {
Expand All @@ -30,7 +53,7 @@ const DecisionProcessList = ({
string | null
>(null);
const [data] = trpc.decision.listInstances.useSuspenseQuery({
ownerProfileId: profileId,
stewardProfileId: profileId,
limit: 20,
offset: 0,
});
Expand All @@ -43,40 +66,7 @@ const DecisionProcessList = ({
const isProcessAdmin = decisionPermission.create && isOwnProfile;

if (!data.instances || data.instances.length === 0) {
return (
<div className="flex min-h-[400px] flex-col items-center justify-center gap-6 px-6 py-16 text-center">
{isProcessAdmin ? (
<>
<div className="flex size-10 items-center justify-center rounded-full bg-neutral-gray1">
<LuLeaf className="size-6 text-neutral-gray4" />
</div>

<div className="flex max-w-md flex-col gap-2">
<h2 className="font-serif text-title-base text-neutral-black">
{t('Set up your decision-making process')}
</h2>
<p className="text-base text-neutral-charcoal">
{t(
'Create your first participatory budgeting or grantmaking process to start collecting proposals from your community.',
)}
</p>
</div>
</>
) : (
<>
<div className="flex size-10 items-center justify-center rounded-full bg-neutral-gray1">
<LuLeaf className="size-6 text-neutral-gray4" />
</div>

<div className="flex max-w-md flex-col gap-2">
<h2 className="font-serif text-title-base text-neutral-black">
{t('There are no current decision-making processes')}
</h2>
</div>
</>
)}
</div>
);
return null;
}

return (
Expand Down Expand Up @@ -197,6 +187,82 @@ const DecisionProcessList = ({
);
};

const EmptyDecisions = ({ profileId }: { profileId: string }) => {
const t = useTranslations();
const access = useUser();
const { user } = access;
const permission = access.getPermissionsForProfile(profileId);
const isOwnProfile = user.currentProfile?.id === profileId;
const isProcessAdmin = permission.decisions.create && isOwnProfile;

return (
<div className="flex min-h-[400px] flex-col items-center justify-center gap-6 px-6 py-16 text-center">
<div className="flex size-10 items-center justify-center rounded-full bg-neutral-gray1">
<LuLeaf className="size-6 text-neutral-gray4" />
</div>
<div className="flex max-w-md flex-col gap-2">
<h2 className="font-serif text-title-base text-neutral-black">
{isProcessAdmin
? t('Set up your decision-making process')
: t('There are no current decision-making processes')}
</h2>
{isProcessAdmin && (
<p className="text-base text-neutral-charcoal">
{t(
'Create your first participatory budgeting or grantmaking process to start collecting proposals from your community.',
)}
</p>
)}
</div>
</div>
);
};

const DecisionProcessList = ({
profileId,
schema = 'simple',
}: {
profileId: string;
schema?: SchemaType;
}) => {
const access = useUser();
const canReadDecisions =
access.getPermissionsForProfile(profileId).decisions.read;

const [decisionProfiles] =
trpc.decision.listDecisionProfiles.useSuspenseQuery({
stewardProfileId: profileId,
status: VISIBLE_DECISION_STATUSES,
});

const legacyInstances = trpc.decision.listInstances.useQuery(
{ stewardProfileId: profileId, limit: 20, offset: 0 },
{ retry: false, enabled: canReadDecisions },
);

const hasDecisionProfiles = decisionProfiles.items.length > 0;
const hasLegacyInstances = (legacyInstances.data?.instances?.length ?? 0) > 0;

if (!hasDecisionProfiles && !hasLegacyInstances) {
return <EmptyDecisions profileId={profileId} />;
}

return (
<div className="flex flex-col gap-4">
<ErrorBoundary fallback={null}>
<Suspense fallback={null}>
<DecisionProfilesList profileId={profileId} />
</Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={null}>
<Suspense fallback={null}>
<LegacyDecisionProcessList profileId={profileId} schema={schema} />
</Suspense>
</ErrorBoundary>
</div>
);
};

export const ProfileDecisionsSuspense = ({
profileId,
schema = 'simple',
Expand Down
10 changes: 5 additions & 5 deletions apps/app/src/components/decisions/AllDecisions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const DecisionsListSuspense = ({
status,
ownerProfileId,
}: {
status: ProcessStatus;
status: ProcessStatus[];
ownerProfileId?: string;
}) => {
const {
Expand Down Expand Up @@ -112,7 +112,7 @@ const AllDecisionsTabs = () => {

const [draftsCheck] = trpc.decision.listDecisionProfiles.useSuspenseQuery({
limit: 1,
status: ProcessStatus.DRAFT,
status: [ProcessStatus.DRAFT],
ownerProfileId,
});

Expand All @@ -136,7 +136,7 @@ const AllDecisionsTabs = () => {
<TabPanel id="active" className="p-0 sm:p-0">
<Suspense fallback={<DecisionsListSkeleton />}>
<DecisionsListSuspense
status={ProcessStatus.PUBLISHED}
status={[ProcessStatus.PUBLISHED]}
ownerProfileId={ownerProfileId}
/>
</Suspense>
Expand All @@ -145,15 +145,15 @@ const AllDecisionsTabs = () => {
<TabPanel id="drafts" className="p-0 sm:p-0">
<Suspense fallback={<DecisionsListSkeleton />}>
<DecisionsListSuspense
status={ProcessStatus.DRAFT}
status={[ProcessStatus.DRAFT]}
ownerProfileId={ownerProfileId}
/>
</Suspense>
</TabPanel>
)}
<TabPanel id="other" className="p-0 sm:p-0">
<Suspense fallback={<DecisionsListSkeleton />}>
<DecisionsListSuspense status={ProcessStatus.COMPLETED} />
<DecisionsListSuspense status={[ProcessStatus.COMPLETED]} />
</Suspense>
</TabPanel>
</Tabs>
Expand Down
16 changes: 5 additions & 11 deletions apps/app/src/components/screens/Profile/ProfileTabsRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,11 @@ export const ProfileTabsRenderer = ({
organization,
profile,
initialTab,
decisionsEnabled,
schema,
}: {
organization: Organization;
profile: Profile;
initialTab?: string;
decisionsEnabled: boolean;
schema: SchemaType;
}) => {
const isMobile = useMediaQuery(`(max-width: ${screens.sm})`);
Expand Down Expand Up @@ -76,7 +74,7 @@ export const ProfileTabsRenderer = ({
<DesktopOrganizationTabs />
<FollowersTab />
<MembersTab profileId={profile.id} />
{decisionsEnabled && <DecisionsTab profileId={profile.id} />}
<DecisionsTab profileId={profile.id} />
</ProfileTabList>

<TabPanel id="home" className="flex grow flex-col sm:p-0">
Expand All @@ -93,14 +91,10 @@ export const ProfileTabsRenderer = ({
<FollowersTabPanel>
<ProfileFollowers profileId={profile.id} />
</FollowersTabPanel>
{decisionsEnabled && (
<>
<DecisionsTabPanel>
<ProfileDecisionsSuspense profileId={profile.id} schema={schema} />
</DecisionsTabPanel>
<MembersTabPanel profileId={profile.id} />
</>
)}
<DecisionsTabPanel>
<ProfileDecisionsSuspense profileId={profile.id} schema={schema} />
</DecisionsTabPanel>
<MembersTabPanel profileId={profile.id} />
</ProfileTabs>
);
};
Expand Down
Loading