0% found this document useful (0 votes)
25 views10 pages

Random

The document outlines a React component for creating a crisis communication draft, incorporating a multi-step form with validation using Zod and React Hook Form. It includes user authentication checks, draft generation via Supabase functions, and credit management for users. The form captures details like industry, organization, situation, audience, urgency, and content format, with conditional rendering based on user subscription tiers.

Uploaded by

Ismail Hussain
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
25 views10 pages

Random

The document outlines a React component for creating a crisis communication draft, incorporating a multi-step form with validation using Zod and React Hook Form. It includes user authentication checks, draft generation via Supabase functions, and credit management for users. The form captures details like industry, organization, situation, audience, urgency, and content format, with conditional rendering based on user subscription tiers.

Uploaded by

Ismail Hussain
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 10

import { useState } from "react";

import { useNavigate } from "react-router-dom";


import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import Layout from "@/components/layout/Layout";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import {
RadioGroup,
RadioGroupItem
} from "@/components/ui/radio-group";
import { toast } from "@/hooks/use-toast";
import { useAuth } from "@/contexts/AuthContext";
import { CONTENT_FORMATS, INDUSTRIES } from "@/config/constants";
import { ContentFormat } from "@/types";
import {
AlertCircle,
ArrowLeft,
Loader2,
FileText,
MessageCircle,
} from "lucide-react";
import { supabase } from "@/integrations/supabase/client";

// Define the form schema


const formSchema = z.object({
industry: z.string().min(1, "Please select an industry"),
organization: z.string().min(1, "Organization name is required"),
situation: z.string().min(10, "Please provide more detail about the situation"),
audience: z.string().min(1, "Target audience is required"),
urgency: z.enum(["low", "medium", "high"], {
required_error: "Please select an urgency level",
}),
format: z.string().min(1, "Please select a content format"),
additionalInfo: z.string().optional(),
});

type FormValues = z.infer<typeof formSchema>;

const CreateDraft = () => {


const { user, refreshUserData } = useAuth();
const navigate = useNavigate();
const [isGenerating, setIsGenerating] = useState(false);
const [currentStep, setCurrentStep] = useState(1);
const totalSteps = 3;

const form = useForm<FormValues>({


resolver: zodResolver(formSchema),
defaultValues: {
industry: "",
organization: "",
situation: "",
audience: "",
urgency: "medium",
format: "",
additionalInfo: "",
},
mode: "onChange", // Enable validation as user types
});

if (!user) {
return (
<Layout>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<div className="flex flex-col items-center justify-center">
<AlertCircle className="h-12 w-12 text-crisis-600 mb-4" />
<h1 className="text-2xl font-semibold mb-2">Authentication
Required</h1>
<p className="text-gray-600 mb-6">Please log in to create drafts.</p>
<Button onClick={() => navigate("/login")}>Log In</Button>
</div>
</div>
</Layout>
);
}

const onSubmit = async (values: FormValues) => {


if (currentStep < totalSteps) {
// Just move to the next step if we're not on the final step
setCurrentStep(currentStep + 1);
return;
}

if (user.credits < 1) {
toast({
title: "Insufficient Draft Credits",
description: "You need at least 1 draft credit to generate a draft. Please
purchase more draft credits.",
variant: "destructive",
});
navigate("/pricing");
return;
}

// Generate draft
setIsGenerating(true);

try {
console.log("Submitting form values:", values);
// Call the Supabase Edge Function to generate the draft with OpenAI
const { data, error } = await supabase.functions.invoke('generate-draft', {
body: values,
});

console.log("Response from generate-draft function:", data, error);

if (error) throw new Error(error.message);


if (!data.success) throw new Error(data.error || 'Failed to generate draft');

// Save the draft to the database using service role client


const { error: insertError } = await supabase
.from('drafts')
.insert({
user_id: user.id,
title: data.title,
content: data.content,
industry: values.industry,
organization: values.organization,
situation: values.situation,
audience: values.audience,
urgency: values.urgency,
additional_info: values.additionalInfo,
format: values.format,
});

if (insertError) throw new Error(insertError.message);

// Deduct a credit from the user


const newCredits = user.credits - 1;
const { error: updateError } = await supabase
.from('user_profiles')
.update({ credits: newCredits })
.eq('id', user.id);

if (updateError) throw new Error(updateError.message);

// Refresh user data to show updated credit count


await refreshUserData();

toast({
title: "Draft Generated Successfully",
description: "Your crisis communication draft has been created.",
});

navigate("/drafts");
} catch (error) {
console.error("Error generating draft:", error);
toast({
title: "Generation Failed",
description: error instanceof Error ? error.message : "An unexpected error
occurred.",
variant: "destructive",
});
} finally {
setIsGenerating(false);
}
};
const previousStep = () => {
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
}
};

// Function to validate the current step


const validateCurrentStep = async () => {
let isValid = false;

if (currentStep === 1) {
isValid = await form.trigger(['industry', 'organization', 'situation']);
} else if (currentStep === 2) {
isValid = await form.trigger(['audience', 'urgency', 'additionalInfo']);
} else if (currentStep === 3) {
isValid = await form.trigger(['format']);
}

if (isValid) {
if (currentStep < totalSteps) {
setCurrentStep(currentStep + 1);
} else {
await form.handleSubmit(onSubmit)();
}
}
};

// Filter formats based on user's subscription tier


const availableFormats = CONTENT_FORMATS.filter(format => {
switch (format.tierRequired) {
case 'free':
return true;
case 'basic':
return ['basic', 'standard', 'premium'].includes(user.subscription);
case 'standard':
return ['standard', 'premium'].includes(user.subscription);
case 'premium':
return user.subscription === 'premium';
default:
return false;
}
});

return (
<Layout>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Button
variant="ghost"
onClick={() => navigate(-1)}
className="mb-8 flex items-center"
>
<ArrowLeft className="mr-2 h-4 w-4" />
Back
</Button>

<div className="mb-8">
<h1 className="text-3xl font-bold">Create Crisis Communication</h1>
<p className="text-gray-600 mt-2">
Fill out the form below to generate a draft crisis communication
</p>
</div>

{/* Progress Bar */}


<div className="mb-8">
<div className="flex justify-between mb-2">
<span className="text-sm font-medium">Step {currentStep} of
{totalSteps}</span>
<span className="text-sm font-medium">{Math.round((currentStep /
totalSteps) * 100)}% Complete</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div
className="bg-crisis-600 h-2.5 rounded-full"
style={{ width: `${(currentStep / totalSteps) * 100}%` }}
></div>
</div>
</div>

<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
{currentStep === 1 && (
<Card>
<CardContent className="pt-6">
<div className="space-y-6">
<h2 className="text-xl font-semibold">Crisis Context</h2>
<FormField
control={form.control}
name="industry"
render={({ field }) => (
<FormItem>
<FormLabel>Industry</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select your industry" />
</SelectTrigger>
</FormControl>
<SelectContent>
{INDUSTRIES.map((industry) => (
<SelectItem key={industry} value={industry}>
{industry}
</SelectItem>
))}
</SelectContent>
</Select>
<FormDescription>
Select the industry that best matches your
organization.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="organization"
render={({ field }) => (
<FormItem>
<FormLabel>Organization Name</FormLabel>
<FormControl>
<Input placeholder="e.g., Acme Corporation"
{...field} />
</FormControl>
<FormDescription>
The name of your company or organization.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="situation"
render={({ field }) => (
<FormItem>
<FormLabel>Crisis Situation</FormLabel>
<FormControl>
<Textarea
placeholder="Describe the crisis situation in detail"
className="min-h-[120px]"
{...field}
/>
</FormControl>
<FormDescription>
Provide a detailed description of the crisis situation
your organization is facing.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
</Card>
)}

{currentStep === 2 && (


<Card>
<CardContent className="pt-6">
<div className="space-y-6">
<h2 className="text-xl font-semibold">Communication
Details</h2>
<FormField
control={form.control}
name="audience"
render={({ field }) => (
<FormItem>
<FormLabel>Target Audience</FormLabel>
<FormControl>
<Input
placeholder="e.g., Customers, Investors, Employees"
{...field}
/>
</FormControl>
<FormDescription>
Who will be receiving this communication?
</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="urgency"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>Urgency Level</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex flex-col space-y-1"
>
<FormItem className="flex items-center space-x-3
space-y-0">
<FormControl>
<RadioGroupItem value="low" />
</FormControl>
<FormLabel className="font-normal">
Low - Informational update
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3
space-y-0">
<FormControl>
<RadioGroupItem value="medium" />
</FormControl>
<FormLabel className="font-normal">
Medium - Important but not critical
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3
space-y-0">
<FormControl>
<RadioGroupItem value="high" />
</FormControl>
<FormLabel className="font-normal">
High - Urgent situation requiring immediate
action
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="additionalInfo"
render={({ field }) => (
<FormItem>
<FormLabel>Additional Information (Optional)</FormLabel>
<FormControl>
<Textarea
placeholder="Any other relevant details about the
situation"
className="min-h-[100px]"
{...field}
/>
</FormControl>
<FormDescription>
Include any additional context that might help in
crafting your message.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
</Card>
)}

{currentStep === 3 && (


<Card>
<CardContent className="pt-6">
<div className="space-y-6">
<h2 className="text-xl font-semibold">Content Format</h2>

<FormField
control={form.control}
name="format"
render={({ field }) => (
<FormItem>
<FormLabel>Communication Format</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a format" />
</SelectTrigger>
</FormControl>
<SelectContent>
{availableFormats.map((format) => (
<SelectItem key={format.id} value={format.id}>
{format.label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormDescription>
Select the type of communication you need to create.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>

{/* Display unavailable formats */}


{CONTENT_FORMATS.filter(format => !
availableFormats.includes(format)).length > 0 && (
<div className="bg-gray-50 p-4 rounded-md border border-gray-
200">
<h3 className="font-medium text-gray-800 mb-2">Upgrade to
access more formats</h3>
<ul className="space-y-2">
{CONTENT_FORMATS.filter(format => !
availableFormats.includes(format)).map(format => (
<li key={format.id} className="flex items-center text-
gray-600">
<FileText className="h-4 w-4 text-gray-400 mr-2" />
<span>{format.label}</span>
<span className="ml-2 text-xs bg-gray-200 px-2 py-0.5
rounded-full text-gray-700">
{format.tierRequired.charAt(0).toUpperCase() +
format.tierRequired.slice(1)} Plan
</span>
</li>
))}
</ul>
<Button variant="link" className="mt-2 p-0 h-auto"
onClick={() => navigate('/pricing')}>
View upgrade options
</Button>
</div>
)}

{/* Credit usage information */}


<div className="bg-crisis-50 p-4 rounded-md border border-
crisis-100">
<h3 className="font-medium text-crisis-800 flex items-
center">
<MessageCircle className="h-5 w-5 mr-2" />
Draft Credit Usage
</h3>
<p className="mt-2 text-sm text-crisis-700">
Generating this draft will use 1 draft credit from your
account.
</p>
<div className="mt-3 flex items-center text-sm">
<span className="font-medium text-crisis-800">Available
draft credits:</span>
<span className="ml-2">{user?.credits || 0}</span>
</div>
</div>
</div>
</CardContent>
</Card>
)}

<div className="flex justify-between">


{currentStep > 1 ? (
<Button type="button" variant="outline" onClick={previousStep}>
Previous
</Button>
) : (
<div></div>
)}
<Button
type="button"
onClick={validateCurrentStep}
disabled={isGenerating}
>
{isGenerating ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Generating...
</>
) : currentStep < totalSteps ? (
"Continue"
) : (
"Generate Draft"
)}
</Button>
</div>
</form>
</Form>
</div>

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy