diff --git a/client/src/App.tsx b/client/src/App.tsx
index 224e3f7..3e8937d 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -11,6 +11,7 @@ import Authors from "@/pages/authors/Authors";
import WorkReading from "@/pages/works/WorkReading";
import WorkCompare from "@/pages/works/WorkCompare";
import Collections from "@/pages/collections/Collections";
+import CreateCollection from "@/pages/collections/CreateCollection";
import Profile from "@/pages/user/Profile";
import Submit from "@/pages/Submit";
@@ -24,6 +25,7 @@ function Router() {
+
diff --git a/client/src/components/common/AuthorChip.tsx b/client/src/components/common/AuthorChip.tsx
index 29eb0f8..747f84c 100644
--- a/client/src/components/common/AuthorChip.tsx
+++ b/client/src/components/common/AuthorChip.tsx
@@ -3,39 +3,58 @@ import { Author } from "@shared/schema";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
interface AuthorChipProps {
- author: Author;
+ author?: Author;
size?: 'sm' | 'md' | 'lg';
withLifeDates?: boolean;
className?: string;
}
export function AuthorChip({ author, size = 'md', withLifeDates = false, className = '' }: AuthorChipProps) {
- const getAvatarSize = () => {
+ // Helper function to get avatar size class
+ function getAvatarSize() {
switch (size) {
case 'sm': return 'w-8 h-8';
case 'lg': return 'w-12 h-12';
default: return 'w-10 h-10';
}
- };
+ }
- const getTextSize = () => {
+ // Helper function to get text size class
+ function getTextSize() {
switch (size) {
case 'sm': return 'text-sm';
case 'lg': return 'text-lg';
default: return 'text-base';
}
- };
+ }
// Get initials for avatar fallback
- const getInitials = (name: string) => {
+ function getInitials(name: string) {
return name
.split(' ')
.map(part => part.charAt(0))
.join('')
.toUpperCase()
.slice(0, 2);
- };
+ }
+ // If author is undefined, return a placeholder
+ if (!author) {
+ return (
+
+
+ ?
+
+
+
+ Unknown Author
+
+
+
+ );
+ }
+
+ // If author is defined, return the full component
return (
diff --git a/client/src/pages/collections/CreateCollection.tsx b/client/src/pages/collections/CreateCollection.tsx
new file mode 100644
index 0000000..137f248
--- /dev/null
+++ b/client/src/pages/collections/CreateCollection.tsx
@@ -0,0 +1,171 @@
+import { useState } from 'react';
+import { useLocation } from 'wouter';
+import { useMutation } from '@tanstack/react-query';
+import { z } from 'zod';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { queryClient, apiRequest } from '@/lib/queryClient';
+import { useToast } from '@/hooks/use-toast';
+import { PageLayout } from '@/components/layout/PageLayout';
+import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { Textarea } from '@/components/ui/textarea';
+import { Switch } from '@/components/ui/switch';
+import { Button } from '@/components/ui/button';
+import { Loader2 } from 'lucide-react';
+
+// Form validation schema
+const createCollectionSchema = z.object({
+ name: z.string().min(3, { message: 'Collection name must be at least 3 characters' }).max(100),
+ description: z.string().max(500, { message: 'Description must not exceed 500 characters' }).optional(),
+ isPublic: z.boolean().default(true),
+});
+
+type FormValues = z.infer
;
+
+export default function CreateCollection() {
+ const [, navigate] = useLocation();
+ const { toast } = useToast();
+
+ // Initialize form
+ const form = useForm({
+ resolver: zodResolver(createCollectionSchema),
+ defaultValues: {
+ name: '',
+ description: '',
+ isPublic: true,
+ },
+ });
+
+ // Create collection mutation
+ const createMutation = useMutation({
+ mutationFn: async (values: FormValues) => {
+ return await apiRequest<{ id: number; slug: string }>('POST', '/api/collections', {
+ name: values.name,
+ description: values.description || '',
+ isPublic: values.isPublic,
+ userId: 1, // Using mock user ID for demo purposes
+ });
+ },
+ onSuccess: (data) => {
+ toast({
+ title: 'Collection created',
+ description: 'Your collection has been created successfully',
+ });
+ queryClient.invalidateQueries({ queryKey: ['/api/collections'] });
+ navigate(`/collections`);
+ },
+ onError: (error) => {
+ toast({
+ title: 'Error',
+ description: `Failed to create collection: ${error.message}`,
+ variant: 'destructive',
+ });
+ },
+ });
+
+ const onSubmit = (values: FormValues) => {
+ createMutation.mutate(values);
+ };
+
+ return (
+
+
+
+
+ Create a Collection
+
+
+ Collections help you organize literary works into themed groups that you can share with others.
+
+
+
+
+
+
+ );
+}
\ No newline at end of file