Files
fourge-portal/src/pages/client/NewRequest.jsx
T
2026-03-26 23:42:06 -04:00

266 lines
9.9 KiB
React
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import Layout from '../../components/Layout';
import FileAttachment from '../../components/FileAttachment';
import { serviceTypes } from '../../data/mockData';
import { supabase } from '../../lib/supabase';
import { sendEmail } from '../../lib/email';
import { useAuth } from '../../context/AuthContext';
export default function NewRequest() {
const { currentUser } = useAuth();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const preselectedProject = searchParams.get('project') || '';
const [existingProjects, setExistingProjects] = useState([]);
const [submitted, setSubmitted] = useState(false);
const [saving, setSaving] = useState(false);
const [form, setForm] = useState({ project: preselectedProject, serviceType: '', title: '', deadline: '', description: '' });
const [files, setFiles] = useState([]);
const [customProjects, setCustomProjects] = useState([]);
const [isTypingProject, setIsTypingProject] = useState(false);
const [newProjectName, setNewProjectName] = useState('');
useEffect(() => {
async function load() {
if (!currentUser.company_id) return;
const { data: p } = await supabase
.from('projects')
.select('id, name')
.eq('company_id', currentUser.company_id)
.order('created_at', { ascending: false });
setExistingProjects((p || []).map(pr => ({ id: pr.id, name: pr.name })));
}
load();
}, [currentUser.company_id]);
const allProjectNames = [
...existingProjects.map(p => p.name),
...customProjects.filter(name => !existingProjects.some(p => p.name === name)),
];
const set = (field) => (e) => setForm(f => ({ ...f, [field]: e.target.value }));
const handleProjectSelect = (e) => {
if (e.target.value === '__new__') {
setIsTypingProject(true);
setForm(f => ({ ...f, project: '' }));
} else {
setForm(f => ({ ...f, project: e.target.value }));
}
};
const handleAddProject = () => {
const name = newProjectName.trim();
if (!name) return;
if (!customProjects.includes(name) && !existingProjects.some(p => p.name === name)) {
setCustomProjects(prev => [...prev, name]);
}
setForm(f => ({ ...f, project: name }));
setIsTypingProject(false);
setNewProjectName('');
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!currentUser.company_id) {
alert('Your account is not yet assigned to a company. Please contact support.');
return;
}
setSaving(true);
// Find existing project by name within this company, or create new one
let projectId;
const existing = existingProjects.find(p => p.name === form.project);
if (existing) {
projectId = existing.id;
} else {
const { data: newProject } = await supabase.from('projects').insert({
company_id: currentUser.company_id,
name: form.project,
status: 'active',
}).select().single();
projectId = newProject?.id;
}
if (!projectId) { setSaving(false); return; }
// Create task
const { data: task } = await supabase.from('tasks').insert({
project_id: projectId,
title: form.title.trim() || form.serviceType,
status: 'not_started',
current_version: 0,
}).select().single();
if (!task) { setSaving(false); return; }
// Create submission
const { data: submission } = await supabase.from('submissions').insert({
task_id: task.id,
version_number: 1,
type: 'initial',
service_type: form.serviceType,
deadline: form.deadline || null,
description: form.description,
submitted_by: currentUser.id,
submitted_by_name: currentUser.name,
}).select().single();
// Upload files
if (submission && files.length > 0) {
for (const file of files) {
const path = `${task.id}/${Date.now()}_${file.name}`;
const { data: uploaded } = await supabase.storage.from('submissions').upload(path, file);
if (uploaded) {
await supabase.from('submission_files').insert({
submission_id: submission.id,
name: file.name,
storage_path: path,
size: file.size,
});
}
}
}
sendEmail('new_request', 'hello@fourgebranding.com', {
clientName: currentUser.name,
clientEmail: currentUser.email,
company: currentUser.company?.name || '',
serviceType: form.serviceType,
projectName: form.project,
deadline: form.deadline,
description: form.description,
taskId: task.id,
});
setSaving(false);
setSubmitted(true);
};
if (!currentUser.company_id) {
return (
<Layout>
<div style={{ maxWidth: 480, margin: '0 auto', textAlign: 'center', paddingTop: 48 }}>
<div style={{ fontSize: 48, marginBottom: 16 }}>⚠️</div>
<h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 8 }}>Account Not Yet Active</h2>
<p style={{ color: 'var(--text-secondary)', fontSize: 14 }}>
Your account hasn't been linked to a company yet. Please contact the Fourge team to get set up.
</p>
</div>
</Layout>
);
}
if (submitted) {
return (
<Layout>
<div style={{ maxWidth: 480, margin: '0 auto', textAlign: 'center', paddingTop: 48 }}>
<div style={{ fontSize: 48, marginBottom: 16 }}>✅</div>
<h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 8 }}>Request Submitted!</h2>
<p style={{ color: 'var(--text-secondary)', marginBottom: 24, fontSize: 14 }}>
Thanks, {currentUser?.name?.split(' ')[0]}! We've received your request for <strong>{form.serviceType}</strong>
{form.project && <> under <strong>{form.project}</strong></>}.
Our team will review it and update you shortly.
</p>
<div className="action-buttons" style={{ justifyContent: 'center' }}>
<button className="btn btn-primary" onClick={() => navigate('/my-projects')}>View Projects</button>
<button className="btn btn-outline" onClick={() => { setSubmitted(false); setForm({ project: '', serviceType: '', title: '', deadline: '', description: '' }); setFiles([]); }}>
Submit Another
</button>
</div>
</div>
</Layout>
);
}
return (
<Layout>
<div className="page-header">
<div>
<div className="page-title">New Request</div>
<div className="page-subtitle">Tell us what you need and we'll get to work.</div>
</div>
</div>
<div className="card" style={{ maxWidth: 600 }}>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Project *</label>
{isTypingProject ? (
<div style={{ display: 'flex', gap: 8 }}>
<input
type="text"
placeholder="Enter project name..."
value={newProjectName}
onChange={e => setNewProjectName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAddProject(); } }}
autoFocus
style={{ flex: 1 }}
/>
<button type="button" className="btn btn-primary" onClick={handleAddProject} disabled={!newProjectName.trim()}>Add</button>
<button type="button" className="btn btn-outline" onClick={() => { setIsTypingProject(false); setNewProjectName(''); }}>Cancel</button>
</div>
) : (
<select value={form.project} onChange={handleProjectSelect} required>
<option value="">Select a project...</option>
{allProjectNames.map(name => <option key={name} value={name}>{name}</option>)}
<option value="__new__"> Create new project...</option>
</select>
)}
</div>
<div className="grid-2">
<div className="form-group">
<label>Service Type *</label>
<select value={form.serviceType} onChange={set('serviceType')} required>
<option value="">Select a service...</option>
{serviceTypes.map(s => <option key={s} value={s}>{s}</option>)}
</select>
</div>
<div className="form-group">
<label>Desired Deadline</label>
<input type="date" value={form.deadline} onChange={set('deadline')} />
</div>
</div>
<div className="form-group">
<label>
Request Title
<span style={{ fontWeight: 400, color: 'var(--text-muted)', marginLeft: 6 }}>optional — defaults to service type if left blank</span>
</label>
<input
type="text"
placeholder={form.serviceType ? `e.g. ${form.serviceType} — 2026` : 'e.g. Brand Book 2026'}
value={form.title}
onChange={set('title')}
/>
</div>
<div className="form-group">
<label>Project Description *</label>
<textarea
placeholder="Tell us about your project — what you need, your brand, style preferences, any references..."
value={form.description}
onChange={set('description')}
style={{ minHeight: 140 }}
required
/>
</div>
<FileAttachment files={files} onChange={setFiles} />
<div className="notification notification-info" style={{ marginBottom: 16 }}>
Submitting as <strong>{currentUser?.name}</strong> · {currentUser?.company?.name}
</div>
<button type="submit" className="btn btn-primary btn-lg" disabled={saving}>
{saving ? 'Submitting...' : 'Submit Request'}
</button>
</form>
</div>
</Layout>
);
}