File size: 6,699 Bytes
3a7a84c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"use client";

import { useState } from 'react';
import { UseFormReturn } from 'react-hook-form';
import { z } from 'zod';
import { toast } from 'sonner';
import Image from 'next/image';
import { Sparkles, Upload } from 'lucide-react';

import { heroFormSchema } from '@/lib/validators';
import { regenerateImageAction } from '@/app/actions/db.actions';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';


// This helper can live in a utils file. It handles file uploads to a standard API endpoint.
async function uploadFile(file: File): Promise<string> {
    const formData = new FormData();
    formData.append('file', file);
    // This remains a standard fetch call as it's handling multipart/form-data
    const response = await fetch('/api/upload', { method: 'POST', body: formData });
    if (!response.ok) throw new Error('Upload failed');
    const { url } = await response.json();
    return url;
}

type FormSchemaType = z.infer<typeof heroFormSchema>;

interface VisualsSectionProps {
  form: UseFormReturn<FormSchemaType>;
}

export const VisualsSection = ({ form }: VisualsSectionProps) => {
    const [isGeneratingAvatar, setIsGeneratingAvatar] = useState(false);
    const [isGeneratingIcon, setIsGeneratingIcon] = useState(false);

    const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>, fieldName: 'avatarUrl' | 'iconUrl') => {
        const file = e.target.files?.[0];
        if (!file) return;

        const toastId = toast.loading(`Uploading image...`);
        try {
            const url = await uploadFile(file);
            form.setValue(fieldName, url, { shouldValidate: true, shouldDirty: true });
            toast.success("Upload complete!", { id: toastId });
        } catch {
            toast.error("Upload failed.", { id: toastId });
        }
    };

    const handleGenerateImage = async (imageType: 'avatar' | 'icon') => {
        const setLoading = imageType === 'avatar' ? setIsGeneratingAvatar : setIsGeneratingIcon;
        const promptField = imageType === 'avatar' ? 'avatarPrompt' : 'iconPrompt';
        const urlField = imageType === 'avatar' ? 'avatarUrl' : 'iconUrl';

        setLoading(true);
        const toastId = toast.loading(`Generating new ${imageType}...`);
        
        const prompt = form.getValues(promptField);
        const heroName = form.getValues('heroName');

        const result = await regenerateImageAction(prompt, heroName, imageType);

        if (result.success && result.data) {
            form.setValue(urlField, result.data, { shouldValidate: true, shouldDirty: true });
            toast.success(`New ${imageType} generated!`, { id: toastId });
        } else {
            toast.error(result.message || `Could not generate ${imageType}.`, { id: toastId });
        }
        setLoading(false);
    };

    return (
        <div className="p-4 border rounded-lg">
            <h3 className="text-lg font-semibold mb-4">Visuals</h3>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
                {/* Avatar Section */}
                <div className="space-y-2">
                    <FormField
                        control={form.control}
                        name="avatarPrompt"
                        render={({ field }) => (
                          <FormItem>
                            <FormLabel>Avatar Prompt</FormLabel>
                            <FormControl>
                              <Input placeholder="AI prompt for the hero's portrait" {...field} />
                            </FormControl>
                            <FormMessage />
                          </FormItem>
                        )}
                      />
                    <div className="w-full aspect-square relative rounded-md overflow-hidden border bg-muted">
                        {form.watch('avatarUrl') && (
                            <Image src={form.watch('avatarUrl')} alt="Hero Avatar" layout="fill" objectFit="cover" />
                        )}
                    </div>
                    <div className="flex gap-2">
                        <Button asChild variant="outline" className="flex-1 cursor-pointer">
                            <label><Upload className="h-4 w-4 mr-2" /> Upload <Input type="file" className="hidden" accept="image/*" onChange={(e) => handleFileChange(e, 'avatarUrl')} /></label>
                        </Button>
                        <Button type="button" className="flex-1" onClick={() => handleGenerateImage('avatar')} disabled={isGeneratingAvatar}>
                            <Sparkles className="h-4 w-4 mr-2" />
                            {isGeneratingAvatar ? 'Generating...' : 'Generate AI'}
                        </Button>
                    </div>
                </div>

                {/* Icon Section */}
                <div className="space-y-2">
                    <FormField
                        control={form.control}
                        name="iconPrompt"
                        render={({ field }) => (
                          <FormItem>
                            <FormLabel>Icon Prompt</FormLabel>
                            <FormControl>
                              <Input placeholder="AI prompt for the superpower icon" {...field} />
                            </FormControl>
                            <FormMessage />
                          </FormItem>
                        )}
                      />
                    <div className="w-full aspect-square relative rounded-md overflow-hidden border bg-muted p-4">
                         {form.watch('iconUrl') && (
                            <Image src={form.watch('iconUrl')} alt="Superpower Icon" layout="fill" objectFit="contain" />
                        )}
                    </div>
                    <div className="flex gap-2">
                         <Button asChild variant="outline" className="flex-1 cursor-pointer">
                            <label><Upload className="h-4 w-4 mr-2" /> Upload <Input type="file" className="hidden" accept="image/*" onChange={(e) => handleFileChange(e, 'iconUrl')} /></label>
                        </Button>
                        <Button type="button" className="flex-1" onClick={() => handleGenerateImage('icon')} disabled={isGeneratingIcon}>
                            <Sparkles className="h-4 w-4 mr-2" />
                            {isGeneratingIcon ? 'Generating...' : 'Generate AI'}
                        </Button>
                    </div>
                </div>
            </div>
        </div>
    );
};