diff --git a/CHANGELOG.md b/CHANGELOG.md index 82683fd..e2168ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.6.8] - 2025-09-30 + +### Added + +- **Technical Riders**: Complete technical rider management system for touring artists and production teams + - Create and edit comprehensive technical riders with artist information, band members, and contact details + - Manage input/channel lists with mic types, phantom power, and DI requirements + - Define sound system requirements (PA, monitors, console specifications) + - Track backline requirements and artist-provided equipment + - Specify required technical staff and special production requirements + - Add hospitality and additional notes sections + - Share riders with view and edit permissions via secure share links + - Print-friendly PDF exports with professional formatting and proper page breaks + - Available on Production page for easy access +- **Script Import Instructions**: Added LLM prompt template for converting scripts to Run of Show format + - Provides structured instructions for users to convert scripts using external LLMs (ChatGPT, Claude, etc.) + - Template includes guidance for adding lighting cues, audio requirements, and production notes + - Generates JSON format compatible with SoundDocs Run of Show system + - Accessed via Import Show Flow modal in Run of Show editor +- **Trusted By Section**: Added social proof component to landing page + - Live user count display showing platform adoption + - Real-time statistics from user base + - Professional presentation of platform credibility + +### Changed + +- **Web App Version**: Updated to `1.5.6.8` + ## [1.5.6.7] - 2025-09-25 ### Added diff --git a/apps/web/index.html b/apps/web/index.html index 84ffea8..5fe1283 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -85,7 +85,7 @@ "Resource Hub: Reference Guides (Pinouts, Frequency Bands, dB Charts)" ], "operatingSystem": "Web", - "softwareVersion": "1.5.6.7", // Update as your app versions + "softwareVersion": "1.5.6.8", // Update as your app versions "offers": { "@type": "Offer", "price": "0", diff --git a/apps/web/package.json b/apps/web/package.json index 0eb395e..3cf04fe 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,7 +1,7 @@ { "name": "@sounddocs/web", "private": true, - "version": "1.5.6.7", + "version": "1.5.6.8", "type": "module", "scripts": { "dev": "vite", diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index fa34e2a..20b1ee5 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -16,6 +16,8 @@ import ProductionScheduleEditor from "./pages/ProductionScheduleEditor"; import AllProductionSchedules from "./pages/AllProductionSchedules"; import RunOfShowEditor from "./pages/RunOfShowEditor"; import AllRunOfShows from "./pages/AllRunOfShows"; +import RiderEditor from "./pages/RiderEditor"; +import AllRiders from "./pages/AllRiders"; import ShowModePage from "./pages/ShowModePage"; import SharedShowModePage from "./pages/SharedShowModePage"; import PrivacyPolicy from "./pages/PrivacyPolicy"; @@ -23,6 +25,7 @@ import TermsOfService from "./pages/TermsOfService"; import SharedPatchSheet from "./pages/SharedPatchSheet"; import SharedStagePlot from "./pages/SharedStagePlot"; import SharedProductionSchedule from "./pages/SharedProductionSchedule"; +import SharedTechnicalRider from "./pages/SharedTechnicalRider"; import ProfilePage from "./pages/ProfilePage"; import UpdatePasswordPage from "./pages/UpdatePasswordPage"; import SharedWithMePage from "./pages/SharedWithMePage"; @@ -267,6 +270,8 @@ function App() { path="/shared/production-schedule/:shareCode" element={} /> + } /> + } /> } /> + {/* Technical Rider Routes */} + + + + } + /> + + + + } + /> (({ rider }, ref) => { + const formatDate = (dateString?: string) => { + if (!dateString) return "N/A"; + try { + const date = new Date(dateString); + return date.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }); + } catch { + return dateString; + } + }; + + return ( +
+ {/* Header */} +
+
+

SoundDocs

+

Technical Rider

+
+
+

+ {rider.artist_name || "Artist Name"} +

+

+ {rider.genre && {rider.genre} • } + Last Edited: {formatDate(rider.last_edited || rider.created_at)} +

+
+
+ + {/* Contact Information */} +
+

+ Primary Contact +

+ + + + + + + + +
+ Contact Name: {rider.contact_name || "N/A"} + + Email: {rider.contact_email || "N/A"} + + Phone: {rider.contact_phone || "N/A"} +
+
+ + {/* Band Members */} + {rider.band_members && rider.band_members.length > 0 && ( +
+

+ Band Members +

+ + + + + + + + + + {rider.band_members.map((member) => ( + + + + + + ))} + +
NameInstrumentInput Needs
{member.name || "N/A"}{member.instrument || "N/A"}{member.input_needs || "N/A"}
+
+ )} + + {/* Input List */} + {rider.input_list && rider.input_list.length > 0 && ( +
+

+ Input/Channel List +

+ + + + + + + + + + + + + + {rider.input_list.map((input) => ( + + + + + + + + + + ))} + +
ChNameTypeMic48VDINotes
+ {input.channel_number || "-"} + {input.name || "-"}{input.type || "-"}{input.mic_type || "-"} + {input.phantom_power ? "✓" : ""} + + {input.di_needed ? "✓" : ""} + {input.notes || ""}
+
+ )} + + {/* Sound System Requirements */} +
+

+ Sound System Requirements +

+
+ PA System: +

+ {rider.pa_requirements || "Not specified"} +

+
+
+ Monitor System: +

+ {rider.monitor_requirements || "Not specified"} +

+
+
+ Console Requirements: +

+ {rider.console_requirements || "Not specified"} +

+
+
+ + {/* Backline Requirements */} + {rider.backline_requirements && rider.backline_requirements.length > 0 && ( +
+

+ Venue Provided Backline +

+ + + + + + + + + + {rider.backline_requirements.map((item) => ( + + + + + + ))} + +
ItemQtySpecifications
{item.item || "-"}{item.quantity || "-"}{item.notes || "-"}
+
+ )} + + {/* Artist Provided Gear */} + {rider.artist_provided_gear && rider.artist_provided_gear.length > 0 && ( +
+

+ Artist Provided Equipment +

+ + + + + + + + + + {rider.artist_provided_gear.map((item) => ( + + + + + + ))} + +
ItemQtySpecifications
{item.item || "-"}{item.quantity || "-"}{item.notes || "-"}
+
+ )} + + {/* Technical Staff */} + {rider.required_staff && rider.required_staff.length > 0 && ( +
+

+ Required Technical Staff +

+ + + + + + + + + + {rider.required_staff.map((staff) => ( + + + + + + ))} + +
RoleQtyRequirements
{staff.role || "-"}{staff.quantity || "-"}{staff.notes || "-"}
+
+ )} + + {/* Special Requirements */} +
+

+ Special Requirements +

+
+ Stage & Production: +

+ {rider.special_requirements || "None specified"} +

+
+
+ Power Requirements: +

+ {rider.power_requirements || "Standard power"} +

+
+
+ Lighting: +

+ {rider.lighting_notes || "Standard lighting"} +

+
+
+ + {/* Hospitality */} + {rider.hospitality_notes && ( +
+

+ Hospitality +

+

{rider.hospitality_notes}

+
+ )} + + {/* Additional Notes */} + {rider.additional_notes && ( +
+

+ Additional Notes +

+

{rider.additional_notes}

+
+ )} + + {/* Footer */} +
+

Generated with SoundDocs • {new Date().toLocaleDateString()}

+
+
+ ); +}); + +PrintRiderExport.displayName = "PrintRiderExport"; + +export default PrintRiderExport; diff --git a/apps/web/src/components/rider/RiderArtistInfo.tsx b/apps/web/src/components/rider/RiderArtistInfo.tsx new file mode 100644 index 0000000..b94414b --- /dev/null +++ b/apps/web/src/components/rider/RiderArtistInfo.tsx @@ -0,0 +1,239 @@ +import React from "react"; +import { PlusCircle, Trash2 } from "lucide-react"; +import { v4 as uuidv4 } from "uuid"; +import { BandMember } from "../../lib/types"; + +interface RiderArtistInfoProps { + artistName: string; + genre: string; + contactName: string; + contactEmail: string; + contactPhone: string; + bandMembers: BandMember[]; + onUpdateArtistName: (value: string) => void; + onUpdateGenre: (value: string) => void; + onUpdateContactName: (value: string) => void; + onUpdateContactEmail: (value: string) => void; + onUpdateContactPhone: (value: string) => void; + onUpdateBandMembers: (members: BandMember[]) => void; +} + +const RiderArtistInfo: React.FC = ({ + artistName, + genre, + contactName, + contactEmail, + contactPhone, + bandMembers, + onUpdateArtistName, + onUpdateGenre, + onUpdateContactName, + onUpdateContactEmail, + onUpdateContactPhone, + onUpdateBandMembers, +}) => { + const handleAddBandMember = () => { + const newMember: BandMember = { + id: uuidv4(), + name: "", + instrument: "", + input_needs: "", + }; + onUpdateBandMembers([...bandMembers, newMember]); + }; + + const handleDeleteBandMember = (id: string) => { + onUpdateBandMembers(bandMembers.filter((member) => member.id !== id)); + }; + + const handleUpdateBandMember = (id: string, field: keyof BandMember, value: string) => { + onUpdateBandMembers( + bandMembers.map((member) => (member.id === id ? { ...member, [field]: value } : member)), + ); + }; + + return ( +
+ {/* Artist Information Section */} +
+

Artist Information

+

+ Basic information about the artist or band +

+
+ +
+
+ + onUpdateArtistName(e.target.value)} + className="w-full bg-gray-700 text-white border border-gray-600 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="e.g., The Soundwave Collective" + /> +
+ +
+ + onUpdateGenre(e.target.value)} + className="w-full bg-gray-700 text-white border border-gray-600 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="e.g., Rock, Jazz, Electronic" + /> +
+
+ + {/* Contact Information Section */} +
+

Primary Contact

+

Contact person for technical inquiries

+
+ +
+ + onUpdateContactName(e.target.value)} + className="w-full bg-gray-700 text-white border border-gray-600 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="Tour manager or technical contact" + /> +
+ +
+
+ + onUpdateContactEmail(e.target.value)} + className="w-full bg-gray-700 text-white border border-gray-600 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="email@example.com" + /> +
+ +
+ + onUpdateContactPhone(e.target.value)} + className="w-full bg-gray-700 text-white border border-gray-600 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="(555) 123-4567" + /> +
+
+ + {/* Band Members Section */} +
+

Band Members

+

+ List each band member, their instrument, and input requirements +

+
+ +
+ {bandMembers.map((member, index) => ( +
+
+

Member #{index + 1}

+ +
+ +
+
+ + handleUpdateBandMember(member.id, "name", e.target.value)} + className="w-full bg-gray-600 text-white border border-gray-500 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="e.g., John Smith" + /> +
+ +
+ + handleUpdateBandMember(member.id, "instrument", e.target.value)} + className="w-full bg-gray-600 text-white border border-gray-500 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="e.g., Lead Vocals, Guitar" + /> +
+
+ +
+ + handleUpdateBandMember(member.id, "input_needs", e.target.value)} + className="w-full bg-gray-600 text-white border border-gray-500 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="e.g., 2x XLR for vocals and guitar" + /> +
+
+ ))} + + +
+
+ ); +}; + +export default RiderArtistInfo; diff --git a/apps/web/src/components/rider/RiderEquipment.tsx b/apps/web/src/components/rider/RiderEquipment.tsx new file mode 100644 index 0000000..23c9ba8 --- /dev/null +++ b/apps/web/src/components/rider/RiderEquipment.tsx @@ -0,0 +1,298 @@ +import React from "react"; +import { PlusCircle, Trash2 } from "lucide-react"; +import { v4 as uuidv4 } from "uuid"; +import { BacklineItem } from "../../lib/types"; + +interface RiderEquipmentProps { + paRequirements: string; + monitorRequirements: string; + consoleRequirements: string; + backlineRequirements: BacklineItem[]; + artistProvidedGear: BacklineItem[]; + onUpdatePaRequirements: (value: string) => void; + onUpdateMonitorRequirements: (value: string) => void; + onUpdateConsoleRequirements: (value: string) => void; + onUpdateBacklineRequirements: (items: BacklineItem[]) => void; + onUpdateArtistProvidedGear: (items: BacklineItem[]) => void; +} + +const RiderEquipment: React.FC = ({ + paRequirements, + monitorRequirements, + consoleRequirements, + backlineRequirements, + artistProvidedGear, + onUpdatePaRequirements, + onUpdateMonitorRequirements, + onUpdateConsoleRequirements, + onUpdateBacklineRequirements, + onUpdateArtistProvidedGear, +}) => { + const handleAddBacklineItem = () => { + const newItem: BacklineItem = { + id: uuidv4(), + item: "", + quantity: "", + notes: "", + }; + onUpdateBacklineRequirements([...backlineRequirements, newItem]); + }; + + const handleDeleteBacklineItem = (id: string) => { + onUpdateBacklineRequirements(backlineRequirements.filter((item) => item.id !== id)); + }; + + const handleUpdateBacklineItem = (id: string, field: keyof BacklineItem, value: string) => { + onUpdateBacklineRequirements( + backlineRequirements.map((item) => (item.id === id ? { ...item, [field]: value } : item)), + ); + }; + + const handleAddArtistGear = () => { + const newItem: BacklineItem = { + id: uuidv4(), + item: "", + quantity: "", + notes: "", + }; + onUpdateArtistProvidedGear([...artistProvidedGear, newItem]); + }; + + const handleDeleteArtistGear = (id: string) => { + onUpdateArtistProvidedGear(artistProvidedGear.filter((item) => item.id !== id)); + }; + + const handleUpdateArtistGear = (id: string, field: keyof BacklineItem, value: string) => { + onUpdateArtistProvidedGear( + artistProvidedGear.map((item) => (item.id === id ? { ...item, [field]: value } : item)), + ); + }; + + return ( +
+ {/* Sound System Requirements Section */} +
+

Sound System Requirements

+

PA, monitors, and console specifications

+
+ +
+ +