Skip to content
Open
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
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@internationalized/date": "^3.5.4",
"@nextui-org/accordion": "^2.0.28",
"@nextui-org/avatar": "^2.0.21",
"@nextui-org/badge": "^2.2.5",
"@nextui-org/button": "^2.0.21",
"@nextui-org/calendar": "^2.0.7",
"@nextui-org/card": "^2.0.21",
Expand All @@ -30,6 +31,7 @@
"@nextui-org/progress": "^2.0.24",
"@nextui-org/radio": "^2.0.22",
"@nextui-org/scroll-shadow": "^2.1.12",
"@nextui-org/select": "^2.4.9",
"@nextui-org/skeleton": "^2.0.22",
"@nextui-org/spinner": "^2.0.19",
"@nextui-org/switch": "^2.0.33",
Expand All @@ -54,6 +56,7 @@
"firebase-admin": "^11.11.0",
"framer-motion": "^11.3.2",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.545.0",
"next": "^14.0.3",
"next-themes": "^0.2.1",
"next-usequerystate": "^1.10.2",
Expand Down
8 changes: 8 additions & 0 deletions public/icons/user-group.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions src/app/(site)/activity/ActivityClientWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2019-2025 @polkassembly/fellowship authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

'use client';

import React from 'react';
import ActivityFeed from '@/components/Home/ActivityFeed';
import ActivitySelectorCard from '@/components/Home/ActivitySelectorCard';
import { EActivityFeed, ActivityFeedItem, Network } from '@/global/types';

interface Props {
feedItems: ActivityFeedItem[];
feed: EActivityFeed;
network: Network;
originUrl: string;
}

export default function ActivityClientWrapper({ feedItems, feed, network, originUrl }: Props) {
return (
<div className='flex w-full flex-col gap-y-8'>
<ActivitySelectorCard value={feed} />
<ActivityFeed items={feedItems} />
</div>
);
}
50 changes: 50 additions & 0 deletions src/app/(site)/activity/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2019-2025 @polkassembly/fellowship authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { API_ERROR_CODE } from '@/global/constants/errorCodes';
import { ClientError } from '@/global/exceptions';
import MESSAGES from '@/global/messages';
import { EActivityFeed, Network, ServerComponentProps, ActivityFeedItem } from '@/global/types';
import { headers } from 'next/headers';
import { Metadata } from 'next';
import getOriginUrl from '@/utils/getOriginUrl';
import getActivityFeed from '@/app/api/v1/feed/getActivityFeed';
import ActivityClientWrapper from './ActivityClientWrapper';

type SearchParamProps = {
feed: string;
network?: string;
};

export const metadata: Metadata = {
title: 'Activity Feed',
description: 'View fellowship activity feed and recent proposals.'
};

export default async function ActivityPage({ searchParams }: Readonly<ServerComponentProps<unknown, SearchParamProps>>) {
const { feed = EActivityFeed.ALL, network } = searchParams ?? {};

// validate feed search param
if (feed && !Object.values(EActivityFeed).includes(feed as EActivityFeed)) {
throw new ClientError(MESSAGES.INVALID_SEARCH_PARAMS_ERROR, API_ERROR_CODE.INVALID_SEARCH_PARAMS_ERROR);
}

const headersList = headers();
const originUrl = getOriginUrl(headersList);

const feedItems = await getActivityFeed({
feedType: feed as EActivityFeed,
originUrl,
network: network as Network
});

return (
<ActivityClientWrapper
feedItems={feedItems as ActivityFeedItem[]}
feed={feed as EActivityFeed}
network={network as Network}
originUrl={originUrl}
/>
);
}
68 changes: 68 additions & 0 deletions src/app/(site)/create-proposal/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2019-2025 @polkassembly/fellowship authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

'use client';

import CreateProposalForm from '@/components/CreateProposal/CreateProposalForm';
import { Button } from '@nextui-org/button';
import { useRef, useState } from 'react';
import { useUserDetailsContext } from '@/contexts';
import LinkWithNetwork from '@/components/Misc/LinkWithNetwork';

export default function CreateProposal() {
const { id } = useUserDetailsContext();

const formRef = useRef<HTMLFormElement>(null);
const [isFormValid, setIsFormValid] = useState(false);
const [isFormLoading, setIsFormLoading] = useState(false);

const handleSubmit = () => {
if (formRef.current) {
formRef?.current?.requestSubmit();
}
};

return (
<div className='rounded-2xl border border-primary_border p-6'>
<h3 className='mb-3 font-semibold'>Create Proposal</h3>

<div>
{!id ? (
<div className='p-6 text-center'>
Please{' '}
<LinkWithNetwork
href='/login'
className='text-link'
>
login
</LinkWithNetwork>{' '}
to create a proposal.
</div>
) : (
<div className='flex flex-col gap-6'>
<CreateProposalForm
formRef={formRef}
onSuccess={() => {
// Optionally redirect or show success message
}}
onFormStateChange={(isValid, isLoading) => {
setIsFormValid(isValid);
setIsFormLoading(isLoading);
}}
/>

<Button
color='primary'
onPress={handleSubmit}
disabled={!isFormValid || isFormLoading}
className='flex min-h-[40px] flex-1 text-sm'
>
{isFormLoading ? 'Creating...' : 'Create Proposal'}
</Button>
</div>
)}
</div>
</div>
);
}
68 changes: 68 additions & 0 deletions src/app/(site)/submit-evidence/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2019-2025 @polkassembly/fellowship authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

'use client';

import SubmitEvidenceForm from '@/components/SubmitEvidence/SubmitEvidenceForm';
import { Button } from '@nextui-org/button';
import { useRef, useState } from 'react';
import { useUserDetailsContext } from '@/contexts';
import LinkWithNetwork from '@/components/Misc/LinkWithNetwork';

export default function SubmitEvidence() {
const { id } = useUserDetailsContext();

const formRef = useRef<HTMLFormElement>(null);
const [isFormValid, setIsFormValid] = useState(false);
const [isFormLoading, setIsFormLoading] = useState(false);

const handleSubmit = () => {
if (formRef.current) {
formRef?.current?.requestSubmit();
}
};

return (
<div className='rounded-2xl border border-primary_border p-6'>
<h3 className='mb-3 font-semibold'>Submit Evidence</h3>

<div>
{!id ? (
<div className='p-6 text-center'>
Please{' '}
<LinkWithNetwork
href='/login'
className='text-link'
>
login
</LinkWithNetwork>{' '}
to submit evidence.
</div>
) : (
<div className='flex flex-col gap-6'>
<SubmitEvidenceForm
formRef={formRef}
onSuccess={() => {
// Optionally redirect or show success message
}}
onFormStateChange={(isValid, isLoading) => {
setIsFormValid(isValid);
setIsFormLoading(isLoading);
}}
/>

<Button
color='primary'
onPress={handleSubmit}
disabled={!isFormValid || isFormLoading}
className='flex min-h-[40px] flex-1 text-sm'
>
{isFormLoading ? 'Submitting...' : 'Submit Evidence'}
</Button>
</div>
)}
</div>
</div>
);
}
97 changes: 97 additions & 0 deletions src/app/@modal/(site)/(.)create-proposal/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2019-2025 @polkassembly/fellowship authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

'use client';

import CreateProposalForm from '@/components/CreateProposal/CreateProposalForm';
import { Button } from '@nextui-org/button';
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/modal';
import { useRouter } from 'next/navigation';
import React, { useRef, useState } from 'react';
import { Divider } from '@nextui-org/divider';
import { useUserDetailsContext } from '@/contexts';
import LinkWithNetwork from '@/components/Misc/LinkWithNetwork';
import { FileText } from 'lucide-react';

function CreateProposalModal() {
const router = useRouter();
const { id } = useUserDetailsContext();

const formRef = useRef<HTMLFormElement>(null);
const [isModalOpen, setIsModalOpen] = useState(true);
const [isFormValid, setIsFormValid] = useState(false);
const [isFormLoading, setIsFormLoading] = useState(false);

const handleOnClose = () => {
router.back();
};

const handleSubmit = () => {
if (formRef.current) {
formRef?.current?.requestSubmit();
}
};

return (
<Modal
isOpen={isModalOpen}
onClose={handleOnClose}
size='5xl'
scrollBehavior='inside'
shouldBlockScroll
className='bg-cardBg'
>
<ModalContent>
{() =>
id ? (
<>
<ModalHeader className='flex items-center gap-2 text-sm'>
<FileText className='h-6 w-6 font-semibold' />
<h3 className='font-semibold'>Create Proposal</h3>
</ModalHeader>
<Divider />

<ModalBody className='p-6'>
<CreateProposalForm
formRef={formRef}
onSuccess={() => setIsModalOpen(false)}
onFormStateChange={(isValid, isLoading) => {
setIsFormValid(isValid);
setIsFormLoading(isLoading);
}}
/>
Comment on lines +56 to +63
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Route away when the modal succeeds.

Successful proposal creation leaves the modal route in place but with the dialog hidden, so the user hits a blank screen. Mirror the regular close path by sending the router back after setting the modal closed.

-									onSuccess={() => setIsModalOpen(false)}
+									onSuccess={() => {
+										setIsModalOpen(false);
+										router.back();
+									}}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<CreateProposalForm
formRef={formRef}
onSuccess={() => setIsModalOpen(false)}
onFormStateChange={(isValid, isLoading) => {
setIsFormValid(isValid);
setIsFormLoading(isLoading);
}}
/>
<CreateProposalForm
formRef={formRef}
onSuccess={() => {
setIsModalOpen(false);
router.back();
}}
onFormStateChange={(isValid, isLoading) => {
setIsFormValid(isValid);
setIsFormLoading(isLoading);
}}
/>
🤖 Prompt for AI Agents
In src/app/@modal/(site)/(.)create-proposal/page.tsx around lines 56 to 63, the
onSuccess handler only closes the modal state leaving the modal route in place
which shows a blank page; update the handler to both close the modal AND
navigate the router back to the parent route (mirror the regular close path).
Import and use Next's router (e.g., const router = useRouter() from
'next/navigation') and change onSuccess to call setIsModalOpen(false) followed
by router.back() (or router.push to the appropriate parent path) so the route is
removed after successful creation.

</ModalBody>

<Divider />

<ModalFooter>
<Button
color='primary'
onPress={handleSubmit}
disabled={!isFormValid || isFormLoading}
className='flex flex-1 bg-primary_accent text-sm'
>
{isFormLoading ? 'Creating...' : 'Create Proposal'}
</Button>
</ModalFooter>
</>
) : (
<div className='p-6 text-center'>
Please{' '}
<LinkWithNetwork
href='/login'
className='text-link'
>
login
</LinkWithNetwork>{' '}
to create a proposal.
</div>
)
}
</ModalContent>
</Modal>
);
}

export default CreateProposalModal;
Loading