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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 9 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ jobs:
run: npm ci

- name: Build project
run: npm run build
run: npm run build
env:
# Bypass compile-time validation of app env. Vercel validates at runtime.
SKIP_ENV_VALIDATION: "true"
# Dummy values for vendor SDKs that read signing keys at module import
# time (e.g. @upstash/qstash's verifySignatureAppRouter). CI only needs
# these to satisfy the collect-page-data step; real values live in Vercel.
QSTASH_CURRENT_SIGNING_KEY: "build-placeholder"
QSTASH_NEXT_SIGNING_KEY: "build-placeholder"
171 changes: 171 additions & 0 deletions app/[locale]/about/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { ExternalLink } from "lucide-react"
import { useTranslations } from 'next-intl'

export default function AboutPage() {
const t = useTranslations('About')

return (
<div className="container mx-auto space-y-8 py-8">
{/* Hero Section */}
<div className="relative h-[400px] overflow-hidden rounded-lg">
<div className="absolute inset-0">
<img
src="parliament.jpg"
alt="Pakistan Parliament"
className="size-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/50 to-black/70" />
</div>
<div className="relative z-10 flex h-full flex-col items-center justify-center p-6 text-center text-white">
<h1 className="mb-4 scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
{t('title')}
</h1>
<p className="text-xl text-white/80">
{t('subtitle')}
</p>
</div>
</div>

{/* Journey Section */}
<div className="grid gap-6 md:grid-cols-3">
<Card>
<CardHeader>
<CardTitle>{t('originalVision')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
{t('originalVisionDesc')}
</p>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle>{t('pivot')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
{t('pivotDesc')}
</p>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle>{t('today')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
{t('todayDesc')}
</p>
</CardContent>
</Card>
</div>

{/* Featured Article */}
<Card className="w-full">
<CardHeader>
<CardTitle>{t('mediaTitle')}</CardTitle>
</CardHeader>
<CardContent className="flex flex-col items-center gap-6 md:flex-row">
<img
src="https://codeforpakistan.org/sites/default/files/cfp_logotype_h.png"
alt="Code for Pakistan Logo"
className="size-24 object-contain"
/>
<div className="space-y-4">
<h3 className="text-2xl font-semibold">{t('mediaArticleTitle')}</h3>
<p className="text-muted-foreground">
{t('mediaArticleDesc')}
</p>
<Button variant="outline" asChild>
<a
href="https://codeforpakistan.org/stories/say-hello-to-my-new-friend"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2"
>
{t('readMore')}
<ExternalLink className="size-4" />
</a>
</Button>
</div>
</CardContent>
</Card>

{/* Key Features */}
<div className="space-y-4">
<h2 className="text-2xl font-bold">{t('featuresTitle')}</h2>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<Card>
<CardHeader>
<CardTitle className="text-center">{t('features.representativesTitle')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-center text-muted-foreground">
{t('features.representativesDesc')}
</p>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle className="text-center">{t('features.locationTitle')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-center text-muted-foreground">
{t('features.locationDesc')}
</p>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle className="text-center">{t('features.chatTitle')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-center text-muted-foreground">
{t('features.chatDesc')}
</p>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle className="text-center">{t('features.availableTitle')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-center text-muted-foreground">
{t('features.availableDesc')}
</p>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle className="text-center">{t('features.searchTitle')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-center text-muted-foreground">
{t('features.searchDesc')}
</p>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle className="text-center">{t('features.languageTitle')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-center text-muted-foreground">
{t('features.languageDesc')}
</p>
</CardContent>
</Card>
</div>
</div>
</div>
)
}
187 changes: 187 additions & 0 deletions app/[locale]/admin/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
'use client';

import { useEffect, useState } from 'react';
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress";
import { Skeleton } from "@/components/ui/skeleton";
import { formatDistanceToNow } from 'date-fns';

type UploadStatus = 'pending' | 'processing' | 'completed' | 'failed';

interface DocumentUpload {
id: string;
status: UploadStatus;
originalFileName: string;
fileSize: number;
uploadProgress: number;
processingProgress: number;
error: string | null;
createdAt: string;
updatedAt: string;
}

export default function DashboardPage() {
const [uploads, setUploads] = useState<DocumentUpload[]>([]);
const [loading, setLoading] = useState(false);
const [isAuthorized, setIsAuthorized] = useState(false);
const [password, setPassword] = useState('');
const [error, setError] = useState('');

const checkPassword = async (inputPassword: string) => {
try {
const response = await fetch('/api/admin/check', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ password: inputPassword }),
});

if (response.ok) {
setIsAuthorized(true);
sessionStorage.setItem('adminAuthorized', 'true');
fetchUploads();
} else {
setError('Invalid password');
}
} catch (error) {
setError('Something went wrong');
}
};

const fetchUploads = async () => {
try {
setLoading(true);
const response = await fetch('/api/admin/uploads');
const data = await response.json();
setUploads(data);
} catch (error) {
console.error('Failed to fetch uploads:', error);
} finally {
setLoading(false);
}
};

useEffect(() => {
if (sessionStorage.getItem('adminAuthorized') === 'true') {
setIsAuthorized(true);
fetchUploads();
}
}, []);

if (!isAuthorized) {
return (
<div className="container max-w-md py-8">
<h1 className="mb-8 text-2xl font-bold">Admin Access</h1>
<div className="space-y-4">
<div className="space-y-2">
<label htmlFor="password" className="block text-sm font-medium">
Password
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-md border border-input bg-background px-3 py-2"
/>
</div>
<Button onClick={() => checkPassword(password)}>Submit</Button>
{error && <p className="text-red-500">{error}</p>}
</div>
</div>
);
}

const getStatusBadge = (status: UploadStatus) => {
const variants = {
pending: 'secondary',
processing: 'default',
completed: 'success',
failed: 'destructive',
};

return (
<Badge variant={variants[status] as any}>
{status.charAt(0).toUpperCase() + status.slice(1)}
</Badge>
);
};

const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};

return (
<div className="container py-8">
<div className="flex justify-between items-center mb-8">
<h1 className="text-2xl font-bold">Upload Dashboard</h1>
<Button onClick={fetchUploads}>Refresh</Button>
</div>

<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>File Name</TableHead>
<TableHead>Status</TableHead>
<TableHead>Size</TableHead>
<TableHead>Upload Progress</TableHead>
<TableHead>Processing Progress</TableHead>
<TableHead>Created</TableHead>
<TableHead>Error</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
[...Array(5)].map((_, index) => (
<TableRow key={index}>
<TableCell><Skeleton className="h-4 w-40" /></TableCell>
<TableCell><Skeleton className="h-5 w-20 rounded-full" /></TableCell>
<TableCell><Skeleton className="h-4 w-16" /></TableCell>
<TableCell><Skeleton className="h-3 w-[100px]" /></TableCell>
<TableCell><Skeleton className="h-3 w-[100px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-24" /></TableCell>
<TableCell><Skeleton className="h-4 w-8" /></TableCell>
</TableRow>
))
) : (
uploads.map((upload) => (
<TableRow key={upload.id}>
<TableCell className="font-medium">{upload.originalFileName}</TableCell>
<TableCell>{getStatusBadge(upload.status)}</TableCell>
<TableCell>{formatFileSize(upload.fileSize)}</TableCell>
<TableCell>
<Progress value={upload.uploadProgress} className="w-[100px]" />
</TableCell>
<TableCell>
<Progress value={upload.processingProgress} className="w-[100px]" />
</TableCell>
<TableCell>
{formatDistanceToNow(new Date(upload.createdAt), { addSuffix: true })}
</TableCell>
<TableCell className="text-red-500">
{upload.error || '-'}
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</div>
);
}
Loading
Loading