document upload redesign
This commit is contained in:
parent
0269cefeeb
commit
c396a00bbc
@ -181,9 +181,9 @@ export class AuthController {
|
|||||||
return this.authService.resetPasswordWithToken(token, newPassword);
|
return this.authService.resetPasswordWithToken(token, newPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("logout")
|
// @Post("logout")
|
||||||
async logout(@Request() req) {
|
// async logout(@Request() req) {
|
||||||
await this.authService.logout(req.user.userId);
|
// await this.authService.logout(req.user.userId);
|
||||||
return { message: "Logged out successfully" };
|
// return { message: "Logged out successfully" };
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
import { Injectable, Logger, NotFoundException, ForbiddenException } from '@nestjs/common';
|
import {
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
Injectable,
|
||||||
import { S3Service } from '../s3/s3.service';
|
Logger,
|
||||||
import { Document, User, Prisma } from '@prisma/client';
|
NotFoundException,
|
||||||
import { EmailService } from '../email/email.service';
|
ForbiddenException,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
|
import { S3Service } from "../s3/s3.service";
|
||||||
|
import { Document, User, Prisma } from "@prisma/client";
|
||||||
|
import { EmailService } from "../email/email.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DocumentsService {
|
export class DocumentsService {
|
||||||
@ -13,8 +18,8 @@ export class DocumentsService {
|
|||||||
private readonly s3Service: S3Service,
|
private readonly s3Service: S3Service,
|
||||||
private readonly emailService: EmailService,
|
private readonly emailService: EmailService,
|
||||||
) {
|
) {
|
||||||
this.logger.log('DocumentsService initialized with EmailService');
|
this.logger.log("DocumentsService initialized with EmailService");
|
||||||
this.logger.debug('EmailService instance:', {
|
this.logger.debug("EmailService instance:", {
|
||||||
exists: !!this.emailService,
|
exists: !!this.emailService,
|
||||||
type: typeof this.emailService,
|
type: typeof this.emailService,
|
||||||
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
||||||
@ -31,16 +36,19 @@ export class DocumentsService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyDocumentAccess(documentId: number, userId: number): Promise<boolean> {
|
async verifyDocumentAccess(
|
||||||
|
documentId: number,
|
||||||
|
userId: number,
|
||||||
|
): Promise<boolean> {
|
||||||
const document = await this.prisma.document.findUnique({
|
const document = await this.prisma.document.findUnique({
|
||||||
where: { id: documentId },
|
where: { id: documentId },
|
||||||
include: {
|
include: {
|
||||||
uploadedBy: true,
|
uploadedBy: true,
|
||||||
sharedWith: {
|
sharedWith: {
|
||||||
where: {
|
where: {
|
||||||
id: userId
|
id: userId,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,31 +68,31 @@ export class DocumentsService {
|
|||||||
{
|
{
|
||||||
sharedWith: {
|
sharedWith: {
|
||||||
some: {
|
some: {
|
||||||
id: clientId
|
id: clientId,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
uploadedBy: {
|
uploadedBy: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
email: true
|
email: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
sharedWith: {
|
sharedWith: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
email: true
|
email: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc'
|
createdAt: "desc",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,10 +100,10 @@ export class DocumentsService {
|
|||||||
file: Express.Multer.File,
|
file: Express.Multer.File,
|
||||||
title: string,
|
title: string,
|
||||||
sharedWithId: number,
|
sharedWithId: number,
|
||||||
uploadedById: number
|
uploadedById: number,
|
||||||
) {
|
) {
|
||||||
this.logger.log('=== Starting document upload process ===');
|
this.logger.log("=== Starting document upload process ===");
|
||||||
this.logger.debug('Upload parameters:', {
|
this.logger.debug("Upload parameters:", {
|
||||||
title,
|
title,
|
||||||
sharedWithId,
|
sharedWithId,
|
||||||
uploadedById,
|
uploadedById,
|
||||||
@ -104,22 +112,22 @@ export class DocumentsService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.logger.debug('Uploading file to S3...');
|
this.logger.debug("Uploading file to S3...");
|
||||||
const s3Key = await this.s3Service.uploadFile(file, 'documents');
|
const s3Key = await this.s3Service.uploadFile(file, "documents");
|
||||||
this.logger.debug(`File uploaded to S3 successfully with key: ${s3Key}`);
|
this.logger.debug(`File uploaded to S3 successfully with key: ${s3Key}`);
|
||||||
|
|
||||||
this.logger.debug('Creating document record in database...');
|
this.logger.debug("Creating document record in database...");
|
||||||
const document = await this.prisma.document.create({
|
const document = await this.prisma.document.create({
|
||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
s3Key,
|
s3Key,
|
||||||
status: 'pending',
|
status: "completed",
|
||||||
uploadedBy: {
|
uploadedBy: {
|
||||||
connect: { id: uploadedById }
|
connect: { id: uploadedById },
|
||||||
},
|
},
|
||||||
sharedWith: {
|
sharedWith: {
|
||||||
connect: { id: sharedWithId }
|
connect: { id: sharedWithId },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
uploadedBy: {
|
uploadedBy: {
|
||||||
@ -138,7 +146,7 @@ export class DocumentsService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.logger.debug('Document record created:', {
|
this.logger.debug("Document record created:", {
|
||||||
id: document.id,
|
id: document.id,
|
||||||
title: document.title,
|
title: document.title,
|
||||||
uploadedBy: document.uploadedBy,
|
uploadedBy: document.uploadedBy,
|
||||||
@ -146,7 +154,7 @@ export class DocumentsService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Send email notifications
|
// Send email notifications
|
||||||
this.logger.log('=== Starting email notification process ===');
|
this.logger.log("=== Starting email notification process ===");
|
||||||
|
|
||||||
// Notify the user who the document is shared with
|
// Notify the user who the document is shared with
|
||||||
const sharedUser = document.sharedWith[0];
|
const sharedUser = document.sharedWith[0];
|
||||||
@ -157,30 +165,32 @@ export class DocumentsService {
|
|||||||
name: sharedUser.name,
|
name: sharedUser.name,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
this.logger.debug('Calling EmailService.sendDocumentNotification for shared user...');
|
this.logger.debug(
|
||||||
|
"Calling EmailService.sendDocumentNotification for shared user...",
|
||||||
|
);
|
||||||
await this.emailService.sendDocumentNotification(
|
await this.emailService.sendDocumentNotification(
|
||||||
sharedUser.email,
|
sharedUser.email,
|
||||||
sharedUser.name,
|
sharedUser.name,
|
||||||
document.title,
|
document.title,
|
||||||
'shared'
|
"shared",
|
||||||
);
|
);
|
||||||
this.logger.debug('Shared user notification sent successfully');
|
this.logger.debug("Shared user notification sent successfully");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Failed to send notification to shared user:', {
|
this.logger.error("Failed to send notification to shared user:", {
|
||||||
error: error.message,
|
error: error.message,
|
||||||
code: error.code,
|
code: error.code,
|
||||||
command: error.command,
|
command: error.command,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
});
|
});
|
||||||
// Log the email service instance to verify it's properly injected
|
// Log the email service instance to verify it's properly injected
|
||||||
this.logger.debug('EmailService instance:', {
|
this.logger.debug("EmailService instance:", {
|
||||||
exists: !!this.emailService,
|
exists: !!this.emailService,
|
||||||
type: typeof this.emailService,
|
type: typeof this.emailService,
|
||||||
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn('No shared user found in the document record');
|
this.logger.warn("No shared user found in the document record");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify the uploader
|
// Notify the uploader
|
||||||
@ -192,36 +202,38 @@ export class DocumentsService {
|
|||||||
name: uploader.name,
|
name: uploader.name,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
this.logger.debug('Calling EmailService.sendDocumentNotification for uploader...');
|
this.logger.debug(
|
||||||
|
"Calling EmailService.sendDocumentNotification for uploader...",
|
||||||
|
);
|
||||||
await this.emailService.sendDocumentNotification(
|
await this.emailService.sendDocumentNotification(
|
||||||
uploader.email,
|
uploader.email,
|
||||||
uploader.name,
|
uploader.name,
|
||||||
document.title,
|
document.title,
|
||||||
'uploaded'
|
"uploaded",
|
||||||
);
|
);
|
||||||
this.logger.debug('Uploader notification sent successfully');
|
this.logger.debug("Uploader notification sent successfully");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Failed to send notification to uploader:', {
|
this.logger.error("Failed to send notification to uploader:", {
|
||||||
error: error.message,
|
error: error.message,
|
||||||
code: error.code,
|
code: error.code,
|
||||||
command: error.command,
|
command: error.command,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
});
|
});
|
||||||
// Log the email service instance to verify it's properly injected
|
// Log the email service instance to verify it's properly injected
|
||||||
this.logger.debug('EmailService instance:', {
|
this.logger.debug("EmailService instance:", {
|
||||||
exists: !!this.emailService,
|
exists: !!this.emailService,
|
||||||
type: typeof this.emailService,
|
type: typeof this.emailService,
|
||||||
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
methods: Object.keys(Object.getPrototypeOf(this.emailService)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn('No uploader found in the document record');
|
this.logger.warn("No uploader found in the document record");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log('=== Document upload process completed ===');
|
this.logger.log("=== Document upload process completed ===");
|
||||||
return document;
|
return document;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Error in uploadDocument:', {
|
this.logger.error("Error in uploadDocument:", {
|
||||||
error: error.message,
|
error: error.message,
|
||||||
code: error.code,
|
code: error.code,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from "react";
|
||||||
import { uploadDocument, getAllUsers, getUserInfo } from '../../services/api';
|
import { motion } from "framer-motion";
|
||||||
|
import { FiUpload, FiLoader, FiCheckCircle, FiUsers } from "react-icons/fi";
|
||||||
|
import { uploadDocument, getAllUsers, getUserInfo } from "../../services/api";
|
||||||
|
import { Card } from "../UI/Card";
|
||||||
|
|
||||||
function DocumentUpload() {
|
function DocumentUpload() {
|
||||||
const [file, setFile] = useState(null);
|
const [file, setFile] = useState(null);
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState("");
|
||||||
const [selectedUsers, setSelectedUsers] = useState([]);
|
const [selectedUserId, setSelectedUserId] = useState(""); // Changed from array to single value
|
||||||
const [availableUsers, setAvailableUsers] = useState([]);
|
const [availableUsers, setAvailableUsers] = useState([]);
|
||||||
const [status, setStatus] = useState('idle'); // idle, uploading, completed, failed
|
const [status, setStatus] = useState("idle");
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
const [currentUser, setCurrentUser] = useState(null);
|
const [currentUser, setCurrentUser] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -15,158 +18,194 @@ function DocumentUpload() {
|
|||||||
getCurrentUser();
|
getCurrentUser();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
|
||||||
try {
|
|
||||||
const response = await getAllUsers();
|
|
||||||
setAvailableUsers(response.data.filter(user => !user.isAdmin));
|
|
||||||
} catch (error) {
|
|
||||||
setErrorMessage('Failed to load users');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCurrentUser = async () => {
|
const getCurrentUser = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await getUserInfo();
|
const response = await getUserInfo();
|
||||||
console.log('Current user data:', response.data); // Debug log
|
|
||||||
setCurrentUser(response.data);
|
setCurrentUser(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to get current user:', error);
|
console.error("Failed to get current user:", error);
|
||||||
setErrorMessage('Failed to get current user info');
|
setErrorMessage("Failed to get current user info");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchUsers = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getAllUsers();
|
||||||
|
setAvailableUsers(response.data.filter((user) => !user.isAdmin));
|
||||||
|
} catch (error) {
|
||||||
|
setErrorMessage("Failed to load users");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!file || !title || selectedUsers.length === 0 || !currentUser) {
|
if (!file || !title || !selectedUserId || !currentUser?.id) {
|
||||||
setErrorMessage('Please provide all required information');
|
setErrorMessage("Please provide all required information");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus('uploading');
|
// Convert IDs to numbers
|
||||||
setErrorMessage('');
|
const sharedWithId = parseInt(selectedUserId, 10);
|
||||||
|
const uploadedById = parseInt(currentUser.id, 10);
|
||||||
|
|
||||||
|
if (isNaN(sharedWithId) || isNaN(uploadedById)) {
|
||||||
|
setErrorMessage("Invalid user IDs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus("uploading");
|
||||||
|
setErrorMessage("");
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append("file", file);
|
||||||
formData.append('title', title);
|
formData.append("title", title);
|
||||||
formData.append('sharedWithId', selectedUsers[0]); // Remove toString()
|
formData.append("sharedWithId", sharedWithId);
|
||||||
formData.append('uploadedById', currentUser.id); // Remove toString()
|
formData.append("uploadedById", uploadedById);
|
||||||
|
|
||||||
// Debug log
|
// Debug log
|
||||||
console.log('Form Data Contents:', {
|
console.log("Form Data:", {
|
||||||
file: formData.get('file'),
|
title,
|
||||||
title: formData.get('title'),
|
sharedWithId,
|
||||||
sharedWithId: formData.get('sharedWithId'),
|
uploadedById,
|
||||||
uploadedById: formData.get('uploadedById')
|
fileName: file.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await uploadDocument(formData);
|
await uploadDocument(formData);
|
||||||
console.log('Upload response:', response);
|
setStatus("completed");
|
||||||
|
setTitle("");
|
||||||
setStatus('completed');
|
|
||||||
setTitle('');
|
|
||||||
setFile(null);
|
setFile(null);
|
||||||
setSelectedUsers([]);
|
setSelectedUserId("");
|
||||||
event.target.reset();
|
event.target.reset();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Upload error:', error);
|
console.error("Upload error:", error);
|
||||||
setStatus('failed');
|
setStatus("failed");
|
||||||
setErrorMessage(error.response?.data?.message || 'Upload failed');
|
setErrorMessage(error.response?.data?.message || "Upload failed");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleFileChange(e) {
|
|
||||||
setFile(e.target.files[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUserSelect = (e) => {
|
|
||||||
const values = Array.from(e.target.selectedOptions, option => option.value);
|
|
||||||
console.log('Selected user values:', values);
|
|
||||||
setSelectedUsers(values);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md">
|
<div className="min-h-screen bg-gradient-to-br from-primary-900 to-primary-800 p-6">
|
||||||
<h2 className="text-2xl font-bold mb-6">Upload Document</h2>
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<header className="mb-8 mt-20">
|
||||||
|
<h1 className="text-3xl font-bold text-white mb-2">
|
||||||
|
Upload Document
|
||||||
|
</h1>
|
||||||
|
<p className="text-neutral-400">Share documents with your clients</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<motion.div
|
||||||
<div>
|
initial={{ opacity: 0, y: 20 }}
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
animate={{ opacity: 1, y: 0 }}
|
||||||
Document Title
|
transition={{ duration: 0.5 }}
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={title}
|
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
|
||||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
|
||||||
File
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
onChange={handleFileChange}
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
|
||||||
Share with Users
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
multiple
|
|
||||||
value={selectedUsers}
|
|
||||||
onChange={handleUserSelect}
|
|
||||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
{availableUsers.map(user => (
|
|
||||||
<option key={user.id} value={user.id}>
|
|
||||||
{user.name} ({user.email})
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{errorMessage && (
|
|
||||||
<div className="text-red-500 text-sm mt-2">
|
|
||||||
{errorMessage}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={status === 'uploading'}
|
|
||||||
className={`w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white
|
|
||||||
${status === 'uploading'
|
|
||||||
? 'bg-gray-400 cursor-not-allowed'
|
|
||||||
: 'bg-indigo-600 hover:bg-indigo-700'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{status === 'uploading' ? (
|
<Card className="bg-neutral-900/80 backdrop-blur-lg border border-neutral-800">
|
||||||
<>
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
{/* Title Input */}
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
<div>
|
||||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
<label className="block text-sm font-medium text-white mb-2">
|
||||||
</svg>
|
Document Title
|
||||||
Uploading...
|
</label>
|
||||||
</>
|
<input
|
||||||
) : 'Upload Document'}
|
type="text"
|
||||||
</button>
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
className="w-full px-4 py-2 rounded-lg bg-neutral-800/50 border border-neutral-700
|
||||||
|
text-white placeholder-neutral-400 focus:outline-none focus:border-primary-500"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{status === 'completed' && (
|
{/* File Input */}
|
||||||
<div className="text-green-500 text-sm mt-2">
|
<div>
|
||||||
Document uploaded successfully!
|
<label className="block text-sm font-medium text-white mb-2">
|
||||||
</div>
|
File
|
||||||
)}
|
</label>
|
||||||
</form>
|
<div className="flex items-center justify-center w-full">
|
||||||
|
<label
|
||||||
|
className="w-full flex flex-col items-center px-4 py-6 rounded-lg
|
||||||
|
border-2 border-dashed border-neutral-700 hover:border-primary-500
|
||||||
|
cursor-pointer transition-colors"
|
||||||
|
>
|
||||||
|
<FiUpload className="w-8 h-8 text-neutral-400" />
|
||||||
|
<span className="mt-2 text-sm text-neutral-400">
|
||||||
|
{file ? file.name : "Select a file"}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
className="hidden"
|
||||||
|
onChange={(e) => setFile(e.target.files[0])}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* User Selection */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-white mb-2">
|
||||||
|
Share with User
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<FiUsers className="absolute top-3 left-3 text-neutral-400" />
|
||||||
|
<select
|
||||||
|
value={selectedUserId}
|
||||||
|
onChange={(e) => setSelectedUserId(e.target.value)}
|
||||||
|
className="w-full pl-10 px-4 py-2 rounded-lg bg-neutral-800/50 border border-neutral-700
|
||||||
|
text-white focus:outline-none focus:border-primary-500"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="">Select a user</option>
|
||||||
|
{availableUsers.map((user) => (
|
||||||
|
<option key={user.id} value={user.id}>
|
||||||
|
{user.name} ({user.email})
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error Message */}
|
||||||
|
{errorMessage && (
|
||||||
|
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-lg text-red-200">
|
||||||
|
{errorMessage}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Success Message */}
|
||||||
|
{status === "completed" && (
|
||||||
|
<div className="p-4 bg-green-500/10 border border-green-500/20 rounded-lg text-green-200">
|
||||||
|
Document uploaded successfully!
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={status === "uploading"}
|
||||||
|
className="w-full flex items-center justify-center px-4 py-2 rounded-lg
|
||||||
|
bg-primary-600 hover:bg-primary-700 text-white font-medium
|
||||||
|
transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{status === "uploading" ? (
|
||||||
|
<>
|
||||||
|
<FiLoader className="w-5 h-5 mr-2 animate-spin" />
|
||||||
|
Uploading...
|
||||||
|
</>
|
||||||
|
) : status === "completed" ? (
|
||||||
|
<>
|
||||||
|
<FiCheckCircle className="w-5 h-5 mr-2" />
|
||||||
|
Upload Complete
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Upload Document"
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user