ප්‍රධාන අන්තර්ගතයට පනින්න

Next.js Integration

Dropup App Router සහ Server Components ඇතුළුව Next.js සමඟ සම්පූර්ණයෙන් compatible වේ.

ස්ථාපනය

npm install @samithahansaka/dropup

මූලික Setup

Dropup client-side library එකකි, එබැවින් 'use client' directive භාවිතා කරන්න:

// components/FileUploader.tsx
'use client';

import { useDropup } from '@samithahansaka/dropup';

export function FileUploader() {
const { files, getDropProps, getInputProps } = useDropup();

return (
<div {...getDropProps()} className="dropzone">
<input {...getInputProps()} />
<p>ගොනු මෙතැනට දමන්න</p>

<ul>
{files.map(file => (
<li key={file.id}>{file.name}</li>
))}
</ul>
</div>
);
}
// app/upload/page.tsx
import { FileUploader } from '@/components/FileUploader';

export default function UploadPage() {
return (
<main>
<h1>ගොනු Upload කරන්න</h1>
<FileUploader />
</main>
);
}

Uploads සඳහා API Route

App Router (Route Handlers)

// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { writeFile, mkdir } from 'fs/promises';
import path from 'path';

export async function POST(request: NextRequest) {
const formData = await request.formData();
const file = formData.get('file') as File;

if (!file) {
return NextResponse.json(
{ error: 'ගොනුවක් ලබා දී නැත' },
{ status: 400 }
);
}

// Uploads directory නොමැති නම් සාදන්න
const uploadsDir = path.join(process.cwd(), 'public', 'uploads');
await mkdir(uploadsDir, { recursive: true });

// ගොනුව save කරන්න
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
const filename = `${Date.now()}-${file.name}`;
const filepath = path.join(uploadsDir, filename);

await writeFile(filepath, buffer);

return NextResponse.json({
url: `/uploads/${filename}`,
name: file.name,
size: file.size,
});
}

// Body size limit configure කරන්න
export const config = {
api: {
bodyParser: false,
},
};

Pages Router (API Routes)

// pages/api/upload.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import formidable from 'formidable';
import path from 'path';

export const config = {
api: {
bodyParser: false,
},
};

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method allowed නැත' });
}

const form = formidable({
uploadDir: path.join(process.cwd(), 'public', 'uploads'),
keepExtensions: true,
filename: (name, ext) => `${Date.now()}-${name}${ext}`,
});

form.parse(req, (err, fields, files) => {
if (err) {
return res.status(500).json({ error: 'Upload අසාර්ථකයි' });
}

const file = Array.isArray(files.file) ? files.file[0] : files.file;
const filename = path.basename(file.filepath);

res.json({
url: `/uploads/${filename}`,
});
});
}

Upload සහිත Client Component

// components/FullUploader.tsx
'use client';

import { useDropup } from '@samithahansaka/dropup';

export function FullUploader() {
const {
files,
state,
actions,
getDropProps,
getInputProps,
} = useDropup({
accept: 'image/*',
maxSize: 10 * 1024 * 1024,

upload: {
url: '/api/upload',
method: 'POST',
},

onUploadComplete: (file) => {
console.log('Upload විය:', file.uploadedUrl);
},
});

return (
<div className="space-y-4">
<div
{...getDropProps()}
className={`
border-2 border-dashed rounded-lg p-8 text-center
transition-colors cursor-pointer
${state.isDragAccept ? 'border-green-500 bg-green-50' : ''}
${state.isDragReject ? 'border-red-500 bg-red-50' : ''}
${!state.isDragging ? 'border-gray-300 hover:border-gray-400' : ''}
`}
>
<input {...getInputProps()} />
<p className="text-gray-600">
Images මෙතැනට දමන්න හෝ click කරන්න
</p>
</div>

{/* ගොනු ලැයිස්තුව */}
<div className="space-y-2">
{files.map(file => (
<div
key={file.id}
className="flex items-center gap-4 p-3 bg-gray-50 rounded-lg"
>
{file.preview && (
<img
src={file.preview}
alt=""
className="w-12 h-12 object-cover rounded"
/>
)}
<div className="flex-1">
<p className="font-medium">{file.name}</p>
{file.status === 'uploading' && (
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all"
style={{ width: `${file.progress}%` }}
/>
</div>
)}
</div>
<button
onClick={() => actions.remove(file.id)}
className="text-red-500 hover:text-red-700"
>
ඉවත් කරන්න
</button>
</div>
))}
</div>

{/* Upload Button */}
<button
onClick={() => actions.upload()}
disabled={state.isUploading || files.length === 0}
className={`
w-full py-3 rounded-lg font-medium
${state.isUploading || files.length === 0
? 'bg-gray-300 cursor-not-allowed'
: 'bg-blue-600 text-white hover:bg-blue-700'}
`}
>
{state.isUploading
? `උඩුගත වෙමින්... ${state.progress}%`
: 'සියල්ල Upload කරන්න'}
</button>
</div>
);
}

Next.js සමඟ S3 Presigned URL

API Route

// app/api/s3/presign/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});

export async function POST(request: NextRequest) {
const { filename, contentType } = await request.json();
const key = `uploads/${Date.now()}-${filename}`;

const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET!,
Key: key,
ContentType: contentType,
});

const url = await getSignedUrl(s3, command, { expiresIn: 3600 });

return NextResponse.json({
url,
key,
publicUrl: `https://${process.env.S3_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`,
});
}

Client Component

// components/S3Uploader.tsx
'use client';

import { useDropup } from '@samithahansaka/dropup';
import { createS3Uploader } from '@samithahansaka/dropup/cloud/s3';

export function S3Uploader() {
const { files, getDropProps, getInputProps } = useDropup({
upload: createS3Uploader({
getPresignedUrl: async (file) => {
const response = await fetch('/api/s3/presign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
return response.json();
},
}),
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>S3 වෙත Upload කරන්න</p>
</div>
);
}

Server Actions (Next.js 14+)

// app/actions.ts
'use server';

import { writeFile } from 'fs/promises';
import path from 'path';

export async function uploadFile(formData: FormData) {
const file = formData.get('file') as File;

if (!file) {
throw new Error('ගොනුවක් ලබා දී නැත');
}

const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);

const filename = `${Date.now()}-${file.name}`;
const filepath = path.join(process.cwd(), 'public', 'uploads', filename);

await writeFile(filepath, buffer);

return {
url: `/uploads/${filename}`,
};
}
// components/ServerActionUploader.tsx
'use client';

import { useDropup } from '@samithahansaka/dropup';
import { uploadFile } from '@/app/actions';

export function ServerActionUploader() {
const { files, actions, getDropProps, getInputProps } = useDropup({
upload: async (file, options) => {
const formData = new FormData();
formData.append('file', file.file);

const result = await uploadFile(formData);

return { url: result.url };
},
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Server Actions සමඟ Upload කරන්න</p>
</div>
);
}

Environment Variables

# .env.local
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
S3_BUCKET=your-bucket-name

SSR සලකා බැලීම්

Dropup SSR-safe වේ. Browser APIs ලබා ගත හැකි විට පමණක් භාවිතා කරයි:

// මෙය SSR සමඟ හොඳින් ක්‍රියා කරයි
'use client';

import { useDropup } from '@samithahansaka/dropup';

export function Uploader() {
// Hook client-side පමණක් run වේ
const { files } = useDropup();
// ...
}

TypeScript Configuration

Next.js සමඟ සම්පූර්ණ type support සඳහා:

// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "bundler",
"strict": true
}
}

Upload Routes සඳහා Middleware

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
// Upload routes සඳහා auth check එකතු කරන්න
if (request.nextUrl.pathname.startsWith('/api/upload')) {
const token = request.headers.get('authorization');

if (!token) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
}

return NextResponse.next();
}

export const config = {
matcher: '/api/upload/:path*',
};