Fix bulk CSV processing and improve user registration
- Fix bulk CSV upload functionality that was returning HTML errors - Implement proper project/organization handling for logged-in vs anonymous users - Update user registration to create unique Default Organization and Default Project - Fix frontend API URL configuration for bulk upload endpoints - Resolve foreign key constraint violations in bulk processing - Update BulkProcessorService to use in-memory processing instead of Redis - Fix redirect-tracker service to handle missing project IDs properly - Update Prisma schema for optional project relationships in bulk jobs - Improve registration form UI with better password validation and alignment
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
apps/web/dist/index.html
vendored
2
apps/web/dist/index.html
vendored
@@ -15,7 +15,7 @@
|
||||
|
||||
gtag('config', 'G-ZDZ26XYN2P');
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-DL4PyATX.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BpAHoLvP.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -60,6 +60,9 @@ import {
|
||||
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
|
||||
// Get API base URL from environment
|
||||
const API_BASE_URL = (import.meta as any).env?.VITE_API_URL || 'http://localhost:3334';
|
||||
|
||||
const bulkUploadSchema = z.object({
|
||||
projectId: z.string().min(1, 'Project ID is required'),
|
||||
enableSSLAnalysis: z.boolean(),
|
||||
@@ -146,7 +149,7 @@ export function BulkUploadPage() {
|
||||
} = useQuery({
|
||||
queryKey: ['bulkJobs'],
|
||||
queryFn: async () => {
|
||||
const response = await fetch('/api/v2/bulk/jobs', {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v2/bulk/jobs`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to fetch jobs');
|
||||
@@ -169,7 +172,7 @@ export function BulkUploadPage() {
|
||||
formData.append('maxHops', data.maxHops.toString());
|
||||
formData.append('timeout', data.timeout.toString());
|
||||
|
||||
const response = await fetch('/api/v2/bulk/upload', {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v2/bulk/upload`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
body: formData,
|
||||
@@ -184,7 +187,7 @@ export function BulkUploadPage() {
|
||||
onSuccess: (result) => {
|
||||
toast({
|
||||
title: 'Upload successful',
|
||||
description: `Bulk job created: ${result.data.job.id}`,
|
||||
description: `Bulk job created: ${result.data.jobId}`,
|
||||
status: 'success',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
|
||||
@@ -44,8 +44,7 @@ const registerSchema = z.object({
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
|
||||
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
|
||||
.regex(/[0-9]/, 'Password must contain at least one number')
|
||||
.regex(/[^A-Za-z0-9]/, 'Password must contain at least one special character'),
|
||||
.regex(/[0-9]/, 'Password must contain at least one number'),
|
||||
confirmPassword: z.string(),
|
||||
}).refine((data) => data.password === data.confirmPassword, {
|
||||
message: "Passwords don't match",
|
||||
@@ -91,9 +90,23 @@ export function RegisterPage() {
|
||||
// Navigate to dashboard after successful registration
|
||||
navigate('/dashboard', { replace: true });
|
||||
} catch (error: any) {
|
||||
setError('root', {
|
||||
message: error.response?.data?.message || 'Registration failed. Please try again.',
|
||||
});
|
||||
console.error('Registration error:', error);
|
||||
|
||||
let errorMessage = 'Registration failed. Please try again.';
|
||||
|
||||
if (error.response?.data?.message) {
|
||||
errorMessage = error.response.data.message;
|
||||
} else if (error.response?.data?.details) {
|
||||
// Handle validation errors
|
||||
const details = error.response.data.details;
|
||||
if (Array.isArray(details) && details.length > 0) {
|
||||
errorMessage = details[0].message || errorMessage;
|
||||
}
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
setError('root', { message: errorMessage });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -109,11 +122,10 @@ export function RegisterPage() {
|
||||
{ label: 'One uppercase letter', valid: /[A-Z]/.test(password || '') },
|
||||
{ label: 'One lowercase letter', valid: /[a-z]/.test(password || '') },
|
||||
{ label: 'One number', valid: /[0-9]/.test(password || '') },
|
||||
{ label: 'One special character', valid: /[^A-Za-z0-9]/.test(password || '') },
|
||||
];
|
||||
|
||||
return (
|
||||
<Container maxW="lg" py={12}>
|
||||
<Container maxW="6xl" py={12}>
|
||||
<VStack spacing={8}>
|
||||
<Box textAlign="center">
|
||||
<Heading as="h1" size="xl" mb={4}>
|
||||
@@ -124,9 +136,9 @@ export function RegisterPage() {
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={8} w="full">
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={8} w="full" alignItems="start">
|
||||
{/* Registration Form */}
|
||||
<Card bg={cardBg} border="1px solid" borderColor={borderColor}>
|
||||
<Card bg={cardBg} border="1px solid" borderColor={borderColor} h="fit-content">
|
||||
<CardBody p={8}>
|
||||
{errors.root && (
|
||||
<Alert status="error" mb={6} borderRadius="md">
|
||||
@@ -161,17 +173,18 @@ export function RegisterPage() {
|
||||
<FormControl isInvalid={!!errors.password}>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<InputGroup>
|
||||
<Input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
placeholder="Create a strong password"
|
||||
autoComplete="new-password"
|
||||
{...register('password')}
|
||||
/>
|
||||
<Input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
placeholder="Create a strong password"
|
||||
autoComplete="new-password"
|
||||
{...register('password')}
|
||||
/>
|
||||
<InputRightElement>
|
||||
<IconButton
|
||||
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
||||
icon={showPassword ? <FiEyeOff /> : <FiEye />}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
/>
|
||||
</InputRightElement>
|
||||
@@ -193,6 +206,7 @@ export function RegisterPage() {
|
||||
aria-label={showConfirmPassword ? 'Hide password' : 'Show password'}
|
||||
icon={showConfirmPassword ? <FiEyeOff /> : <FiEye />}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||
/>
|
||||
</InputRightElement>
|
||||
@@ -207,6 +221,7 @@ export function RegisterPage() {
|
||||
w="full"
|
||||
isLoading={isLoading}
|
||||
loadingText="Creating account..."
|
||||
isDisabled={!password || passwordValidations.some(v => !v.valid)}
|
||||
>
|
||||
Create Account
|
||||
</Button>
|
||||
@@ -245,35 +260,35 @@ export function RegisterPage() {
|
||||
</Card>
|
||||
|
||||
{/* Benefits & Password Requirements */}
|
||||
<VStack spacing={6} align="stretch">
|
||||
<VStack spacing={6} align="stretch" h="fit-content">
|
||||
{/* Account Benefits */}
|
||||
<Card bg={cardBg} border="1px solid" borderColor={borderColor}>
|
||||
<CardBody>
|
||||
<Heading size="md" mb={4}>Account Benefits</Heading>
|
||||
<List spacing={2}>
|
||||
<ListItem>
|
||||
<CardBody p={8}>
|
||||
<Heading size="md" mb={6}>Account Benefits</Heading>
|
||||
<List spacing={4}>
|
||||
<ListItem display="flex" alignItems="center">
|
||||
<ListIcon as={FiCheck} color="green.500" />
|
||||
Higher rate limits (1000/hour)
|
||||
<Text>Higher rate limits (1000/hour)</Text>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItem display="flex" alignItems="center">
|
||||
<ListIcon as={FiCheck} color="green.500" />
|
||||
Saved tracking history
|
||||
<Text>Saved tracking history</Text>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItem display="flex" alignItems="center">
|
||||
<ListIcon as={FiCheck} color="green.500" />
|
||||
Analysis dashboards
|
||||
<Text>Analysis dashboards</Text>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItem display="flex" alignItems="center">
|
||||
<ListIcon as={FiCheck} color="green.500" />
|
||||
Organization management
|
||||
<Text>Organization management</Text>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItem display="flex" alignItems="center">
|
||||
<ListIcon as={FiCheck} color="green.500" />
|
||||
API key access
|
||||
<Text>API key access</Text>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItem display="flex" alignItems="center">
|
||||
<ListIcon as={FiCheck} color="green.500" />
|
||||
Bulk URL processing
|
||||
<Text>Bulk URL processing</Text>
|
||||
</ListItem>
|
||||
</List>
|
||||
</CardBody>
|
||||
@@ -282,17 +297,16 @@ export function RegisterPage() {
|
||||
{/* Password Requirements */}
|
||||
{password && (
|
||||
<Card bg={cardBg} border="1px solid" borderColor={borderColor}>
|
||||
<CardBody>
|
||||
<Heading size="md" mb={4}>Password Requirements</Heading>
|
||||
<List spacing={2}>
|
||||
<CardBody p={8}>
|
||||
<Heading size="md" mb={6}>Password Requirements</Heading>
|
||||
<List spacing={4}>
|
||||
{passwordValidations.map((validation, index) => (
|
||||
<ListItem key={index}>
|
||||
<ListItem key={index} display="flex" alignItems="center">
|
||||
<ListIcon
|
||||
as={FiCheck}
|
||||
color={getPasswordValidationColor(validation.valid)}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
color={getPasswordValidationColor(validation.valid)}
|
||||
fontSize="sm"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user