-
+
+ {/* Left Text */}
-
+
Discover projects
-
-
- that are shaping
-
- the future on Stellar
+ {' '}
+ that are shaping the future on Stellar
Validated by the community. Backed milestone by milestone.
+
+ {/* Buttons */}
+
-
-
-
-
-
-
diff --git a/components/organization/OrganizationContent.tsx b/components/organization/OrganizationContent.tsx
index e68ed4faa..593561827 100644
--- a/components/organization/OrganizationContent.tsx
+++ b/components/organization/OrganizationContent.tsx
@@ -2,18 +2,125 @@
import { Search, ArrowUpDown, Plus } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
import OrganizationCard from './cards/OrganzationCards';
import Link from 'next/link';
import { BoundlessButton } from '../buttons';
import { useOrganization } from '@/lib/providers/OrganizationProvider';
import LoadingSpinner from '../LoadingSpinner';
+import { useState, useMemo } from 'react';
+import { useRouter } from 'next/navigation';
+
+type SortOption = 'newest' | 'oldest' | 'name-asc' | 'name-desc';
export default function OrganizationContent() {
- const { organizations, isLoading, isLoadingOrganizations } =
- useOrganization();
+ const router = useRouter();
+ const {
+ organizations,
+ isLoading,
+ isLoadingOrganizations,
+ deleteOrganization,
+ } = useOrganization();
+ const [searchQuery, setSearchQuery] = useState('');
+ const [sortBy, setSortBy] = useState
('newest');
+ const [deletingId, setDeletingId] = useState(null);
+
const loading = isLoading || isLoadingOrganizations;
+
+ // Filter and sort organizations
+ const filteredAndSortedOrganizations = useMemo(() => {
+ let filtered = organizations;
+
+ // Apply search filter
+ if (searchQuery.trim()) {
+ const query = searchQuery.toLowerCase();
+ filtered = organizations.filter(org =>
+ org.name.toLowerCase().includes(query)
+ );
+ }
+
+ // Apply sorting
+ const sorted = [...filtered].sort((a, b) => {
+ switch (sortBy) {
+ case 'newest':
+ return (
+ new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
+ );
+ case 'oldest':
+ return (
+ new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
+ );
+ case 'name-asc':
+ return a.name.localeCompare(b.name);
+ case 'name-desc':
+ return b.name.localeCompare(a.name);
+ default:
+ return 0;
+ }
+ });
+
+ return sorted;
+ }, [organizations, searchQuery, sortBy]);
+
const hasOrganizations = organizations.length > 0;
+ const handleDelete = async (orgId: string) => {
+ if (!confirm('Are you sure you want to delete this organization?')) {
+ return;
+ }
+
+ try {
+ setDeletingId(orgId);
+ await deleteOrganization(orgId);
+ } catch (error) {
+ console.error('Failed to delete organization:', error);
+ alert('Failed to delete organization. Please try again.');
+ } finally {
+ setDeletingId(null);
+ }
+ };
+
+ const handleEdit = (orgId: string) => {
+ router.push(`/organizations/${orgId}/edit`);
+ };
+
+ const handleArchive = async (orgId: string) => {
+ if (!confirm('Are you sure you want to archive this organization?')) {
+ return;
+ }
+
+ try {
+ setDeletingId(orgId);
+ console.log('Archive organization:', orgId, deletingId);
+ alert('Archive functionality will be implemented soon.');
+ } catch (error) {
+ console.error('Failed to archive organization:', error);
+ alert('Failed to archive organization. Please try again.');
+ } finally {
+ setDeletingId(null);
+ }
+ };
+
+ const getSortLabel = () => {
+ switch (sortBy) {
+ case 'newest':
+ return 'Newest First';
+ case 'oldest':
+ return 'Oldest First';
+ case 'name-asc':
+ return 'Name (A-Z)';
+ case 'name-desc':
+ return 'Name (Z-A)';
+ default:
+ return 'Sort';
+ }
+ };
+
if (loading) {
return (
@@ -35,16 +142,53 @@ export default function OrganizationContent() {
setSearchQuery(e.target.value)}
className='focus-visible:border-primary focus-visible:ring-primary w-full rounded-lg border-zinc-800 bg-zinc-900 py-6 pr-4 pl-12 text-white placeholder:text-zinc-500 focus-visible:ring-[1px]'
/>
@@ -65,26 +209,47 @@ export default function OrganizationContent() {
{hasOrganizations && (
-
- {organizations.map(org => (
-
-
-
- ))}
-
+ <>
+ {filteredAndSortedOrganizations.length > 0 ? (
+
+ {filteredAndSortedOrganizations.map(org => (
+
+ ))}
+
+ ) : (
+
+
+
+ No organizations found matching "{searchQuery}"
+
+
+
+
+ )}
+ >
)}
{!hasOrganizations && (
diff --git a/components/organization/OrganizationHeader.tsx b/components/organization/OrganizationHeader.tsx
index 4e0701de3..42114dcc8 100644
--- a/components/organization/OrganizationHeader.tsx
+++ b/components/organization/OrganizationHeader.tsx
@@ -34,12 +34,14 @@ export default function OrganizationHeader() {
-
+
+
+
diff --git a/components/organization/OrganizationSidebar.tsx b/components/organization/OrganizationSidebar.tsx
index 2e5515495..abb1685cb 100644
--- a/components/organization/OrganizationSidebar.tsx
+++ b/components/organization/OrganizationSidebar.tsx
@@ -5,6 +5,7 @@ import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { cn } from '@/lib/utils';
import { useWindowSize } from '@/hooks/use-window-size';
+import { FutureFeature } from '../FeatureFuture';
interface OrganizationSidebarProps {
organizationId?: string;
@@ -42,6 +43,7 @@ export default function OrganizationSidebar({
icon: HandCoins,
label: 'Grants',
href: derivedOrgId ? `/organizations/${derivedOrgId}/grants` : '#',
+ disabled: true,
},
{
icon: Settings,
@@ -71,8 +73,18 @@ export default function OrganizationSidebar({
isValidHref &&
(normalizedPath === item.href ||
normalizedPath?.startsWith(item.href + '/'));
-
- return (
+ return item.disabled ? (
+
+
+
+ {item.label}
+
+
+ ) : (
Host Hackathon
-
+
-
+
diff --git a/components/organization/cards/OrganzationCards.tsx b/components/organization/cards/OrganzationCards.tsx
index 79dfc9b6b..f8691017b 100644
--- a/components/organization/cards/OrganzationCards.tsx
+++ b/components/organization/cards/OrganzationCards.tsx
@@ -1,6 +1,13 @@
'use client';
-import { ArrowRight, HandCoins, Trophy } from 'lucide-react';
+import {
+ MoreVertical,
+ HandCoins,
+ Trophy,
+ Edit,
+ Archive,
+ Trash2,
+} from 'lucide-react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import {
@@ -9,6 +16,13 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import Link from 'next/link';
interface OrganizationCardProps {
id: string;
@@ -23,6 +37,9 @@ interface OrganizationCardProps {
count: number;
applications: number;
};
+ onEdit?: (id: string) => void;
+ onArchive?: (id: string) => void;
+ onDelete?: (id: string) => void;
}
export default function OrganizationCard({
@@ -32,75 +49,147 @@ export default function OrganizationCard({
createdAt,
hackathons,
grants,
+ onEdit,
+ onArchive,
+ onDelete,
}: OrganizationCardProps) {
const router = useRouter();
+
+ const handleEdit = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (onEdit) {
+ onEdit(id);
+ } else {
+ router.push(`/organizations/${id}/edit`);
+ }
+ };
+
+ const handleArchive = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (onArchive) {
+ onArchive(id);
+ } else {
+ if (confirm('Are you sure you want to archive this organization?')) {
+ console.log('Archive organization:', id);
+ // TODO: Implement archive functionality
+ }
+ }
+ };
+
+ const handleDelete = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (onDelete) {
+ onDelete(id);
+ } else {
+ if (confirm('Are you sure you want to delete this organization?')) {
+ console.log('Delete organization:', id);
+ // TODO: Implement delete functionality
+ }
+ }
+ };
+
return (
- router.push(`/organizations/${id}/settings`)}
- className='hover:shadow-primary/10 cursor-pointer rounded-lg border border-zinc-800 bg-black transition-shadow duration-300 hover:shadow-lg'
- >
-
-
-
-
-
- {name}
-
-
- {new Date(createdAt).toLocaleDateString()}
-
+
+
+
+
+
+
+
+ {name}
+
+
+ {new Date(createdAt).toLocaleDateString()}
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ {hackathons.count}
+
-
- {hackathons.count}
-
-
-
-
-
- {hackathons.count} hackathons ({hackathons.submissions}{' '}
- submissions)
-
-
-
+
+
+
+ {hackathons.count} hackathons ({hackathons.submissions}{' '}
+ submissions)
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+ {grants.count}
+
-
- {grants.count}
-
-
-
-
-
- {grants.count} grants ({grants.applications} applications)
-
-
-
+
+
+
+ {grants.count} grants ({grants.applications} applications)
+
+
+
-
+
+
+
+
+
+
+
+ Edit
+
+
+
+ Archive
+
+
+
+ Delete
+
+
+
+
-
-
+
+
);
}
diff --git a/hooks/use-hackathon-publish.ts b/hooks/use-hackathon-publish.ts
index a70215618..b5dc58f5e 100644
--- a/hooks/use-hackathon-publish.ts
+++ b/hooks/use-hackathon-publish.ts
@@ -102,9 +102,8 @@ export const useHackathonPublish = ({
try {
toast.info('Creating escrow contract...');
const escrowPayload = createHackathonEscrow({
- signer: 'GA2XHHR6RMQXPYQGUGXMOAGC5N5RFPM4HPUPOZJCHOO6LINM7DJIQFFJ',
- organizationAddress:
- 'GA2XHHR6RMQXPYQGUGXMOAGC5N5RFPM4HPUPOZJCHOO6LINM7DJIQFFJ',
+ signer: walletAddress,
+ organizationAddress: walletAddress,
hackathonTitle: stepData.information.name || 'Hackathon',
hackathonDescription: stepData.information.description || '',
rewards: stepData.rewards,
@@ -156,7 +155,7 @@ export const useHackathonPublish = ({
toast.info('Funding escrow with prize pool...');
const fundPayload: FundEscrowPayload = {
contractId: contractId,
- signer: 'GA2XHHR6RMQXPYQGUGXMOAGC5N5RFPM4HPUPOZJCHOO6LINM7DJIQFFJ',
+ signer: walletAddress,
amount: totalPrizeAmount,
};
@@ -179,7 +178,7 @@ export const useHackathonPublish = ({
toast.info('Please sign the funding transaction...');
const signedFundXdr = await signTransaction({
unsignedTransaction: fundResponse.unsignedTransaction,
- address: 'GA2XHHR6RMQXPYQGUGXMOAGC5N5RFPM4HPUPOZJCHOO6LINM7DJIQFFJ',
+ address: walletAddress,
});
const fundSendResponse = await sendTransaction(signedFundXdr);