Prettier format pending files
This commit is contained in:
parent
a057dacf56
commit
669ab329a8
10 changed files with 325 additions and 118 deletions
|
|
@ -13,7 +13,9 @@ export default async function handler(req: any, res: any) {
|
||||||
const artistId = query.artist_id;
|
const artistId = query.artist_id;
|
||||||
|
|
||||||
if (!artistId) {
|
if (!artistId) {
|
||||||
return res.status(400).json({ error: "artist_id query parameter is required" });
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: "artist_id query parameter is required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: artist, error: artistError } = await supabase
|
const { data: artist, error: artistError } = await supabase
|
||||||
|
|
@ -39,7 +41,9 @@ export default async function handler(req: any, res: any) {
|
||||||
if (artistError && artistError.code !== "PGRST116") throw artistError;
|
if (artistError && artistError.code !== "PGRST116") throw artistError;
|
||||||
|
|
||||||
if (!artist || !artist.for_hire) {
|
if (!artist || !artist.for_hire) {
|
||||||
return res.status(404).json({ error: "Artist not found or not available for hire" });
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json({ error: "Artist not found or not available for hire" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ export default async function handler(req: any, res: any) {
|
||||||
|
|
||||||
if (!artist_id || !service_type || !description) {
|
if (!artist_id || !service_type || !description) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: "Missing required fields: artist_id, service_type, description",
|
error:
|
||||||
|
"Missing required fields: artist_id, service_type, description",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +42,9 @@ export default async function handler(req: any, res: any) {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (artistError || !artist || !artist.for_hire) {
|
if (artistError || !artist || !artist.for_hire) {
|
||||||
return res.status(404).json({ error: "Artist not found or not available for hire" });
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json({ error: "Artist not found or not available for hire" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create service request
|
// Create service request
|
||||||
|
|
@ -96,7 +99,9 @@ export default async function handler(req: any, res: any) {
|
||||||
if (requester_id) dbQuery = dbQuery.eq("requester_id", requester_id);
|
if (requester_id) dbQuery = dbQuery.eq("requester_id", requester_id);
|
||||||
if (status) dbQuery = dbQuery.eq("status", status);
|
if (status) dbQuery = dbQuery.eq("status", status);
|
||||||
|
|
||||||
const { data, error } = await dbQuery.order("created_at", { ascending: false });
|
const { data, error } = await dbQuery.order("created_at", {
|
||||||
|
ascending: false,
|
||||||
|
});
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return res.json({ data });
|
return res.json({ data });
|
||||||
|
|
@ -109,7 +114,9 @@ export default async function handler(req: any, res: any) {
|
||||||
const { status, notes } = body;
|
const { status, notes } = body;
|
||||||
|
|
||||||
if (!id || !status) {
|
if (!id || !status) {
|
||||||
return res.status(400).json({ error: "Missing required fields: id, status" });
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: "Missing required fields: id, status" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,15 @@ export default async function handler(req: any, res: any) {
|
||||||
|
|
||||||
if (genre) dbQuery = dbQuery.contains("genre", [genre]);
|
if (genre) dbQuery = dbQuery.contains("genre", [genre]);
|
||||||
if (licenseType) dbQuery = dbQuery.eq("license_type", licenseType);
|
if (licenseType) dbQuery = dbQuery.eq("license_type", licenseType);
|
||||||
if (search) dbQuery = dbQuery.or(`title.ilike.%${search}%,description.ilike.%${search}%`);
|
if (search)
|
||||||
|
dbQuery = dbQuery.or(
|
||||||
|
`title.ilike.%${search}%,description.ilike.%${search}%`,
|
||||||
|
);
|
||||||
|
|
||||||
const { data, error, count } = await dbQuery
|
const { data, error, count } = await dbQuery.range(
|
||||||
.range(Number(offset), Number(offset) + Number(limit) - 1);
|
Number(offset),
|
||||||
|
Number(offset) + Number(limit) - 1,
|
||||||
|
);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ export default function EcosystemLicenseModal({
|
||||||
🎵 Welcome to the Ethos Library
|
🎵 Welcome to the Ethos Library
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-slate-400">
|
<DialogDescription className="text-slate-400">
|
||||||
Before you upload your first track, please review and accept the Ecosystem License
|
Before you upload your first track, please review and accept the
|
||||||
|
Ecosystem License
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
|
@ -54,23 +55,28 @@ export default function EcosystemLicenseModal({
|
||||||
<Alert className="bg-blue-500/10 border-blue-500/30">
|
<Alert className="bg-blue-500/10 border-blue-500/30">
|
||||||
<AlertCircle className="h-4 w-4 text-blue-400" />
|
<AlertCircle className="h-4 w-4 text-blue-400" />
|
||||||
<AlertDescription className="text-blue-300">
|
<AlertDescription className="text-blue-300">
|
||||||
This is a one-time agreement. By contributing to the Ethos Library, you're helping
|
This is a one-time agreement. By contributing to the Ethos
|
||||||
build a vibrant community of creators. We're transparent about how your work will be
|
Library, you're helping build a vibrant community of creators.
|
||||||
used.
|
We're transparent about how your work will be used.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
{/* License Terms Section */}
|
{/* License Terms Section */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h3 className="font-semibold text-white">KND-008: AeThex Ecosystem License</h3>
|
<h3 className="font-semibold text-white">
|
||||||
|
KND-008: AeThex Ecosystem License
|
||||||
|
</h3>
|
||||||
|
|
||||||
<div className="bg-slate-800/50 border border-slate-700 rounded-lg p-4 space-y-3 text-sm text-slate-300">
|
<div className="bg-slate-800/50 border border-slate-700 rounded-lg p-4 space-y-3 text-sm text-slate-300">
|
||||||
<p>
|
<p>
|
||||||
<strong className="text-white">What is the Ecosystem License?</strong>
|
<strong className="text-white">
|
||||||
|
What is the Ecosystem License?
|
||||||
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The Ecosystem License allows AeThex development teams (specifically our GameForge
|
The Ecosystem License allows AeThex development teams
|
||||||
arm) to use your track for free in non-commercial projects. This includes:
|
(specifically our GameForge arm) to use your track for free in
|
||||||
|
non-commercial projects. This includes:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul className="list-disc list-inside space-y-2 ml-2">
|
<ul className="list-disc list-inside space-y-2 ml-2">
|
||||||
|
|
@ -86,7 +92,9 @@ export default function EcosystemLicenseModal({
|
||||||
<ul className="list-disc list-inside space-y-2 ml-2">
|
<ul className="list-disc list-inside space-y-2 ml-2">
|
||||||
<li>100% ownership of your music</li>
|
<li>100% ownership of your music</li>
|
||||||
<li>Full credit and attribution</li>
|
<li>Full credit and attribution</li>
|
||||||
<li>Right to license commercially (outside AeThex ecosystem)</li>
|
<li>
|
||||||
|
Right to license commercially (outside AeThex ecosystem)
|
||||||
|
</li>
|
||||||
<li>Ability to use elsewhere without restriction</li>
|
<li>Ability to use elsewhere without restriction</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
@ -94,24 +102,28 @@ export default function EcosystemLicenseModal({
|
||||||
<strong className="text-white">Commercial Use:</strong>
|
<strong className="text-white">Commercial Use:</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you want to license your track for commercial use (outside our ecosystem), you
|
If you want to license your track for commercial use (outside
|
||||||
can set your own price on the NEXUS marketplace or negotiate directly with clients.
|
our ecosystem), you can set your own price on the NEXUS
|
||||||
|
marketplace or negotiate directly with clients.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<strong className="text-white">Getting Help:</strong>
|
<strong className="text-white">Getting Help:</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Our CORP arm can help negotiate high-value commercial licenses and connect you with
|
Our CORP arm can help negotiate high-value commercial licenses
|
||||||
enterprise clients.
|
and connect you with enterprise clients.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<hr className="border-slate-600 my-4" />
|
<hr className="border-slate-600 my-4" />
|
||||||
|
|
||||||
<p className="text-xs text-slate-400">
|
<p className="text-xs text-slate-400">
|
||||||
Version 1.0 | Effective Date: {new Date().toLocaleDateString()} | For complete
|
Version 1.0 | Effective Date: {new Date().toLocaleDateString()}{" "}
|
||||||
legal terms, see our{" "}
|
| For complete legal terms, see our{" "}
|
||||||
<a href="/docs/legal/knd-008" className="text-pink-400 hover:text-pink-300">
|
<a
|
||||||
|
href="/docs/legal/knd-008"
|
||||||
|
className="text-pink-400 hover:text-pink-300"
|
||||||
|
>
|
||||||
full agreement
|
full agreement
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -123,7 +135,9 @@ export default function EcosystemLicenseModal({
|
||||||
<label className="flex items-start gap-3 p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition">
|
<label className="flex items-start gap-3 p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={hasReadTerms}
|
checked={hasReadTerms}
|
||||||
onCheckedChange={(checked) => setHasReadTerms(checked as boolean)}
|
onCheckedChange={(checked) =>
|
||||||
|
setHasReadTerms(checked as boolean)
|
||||||
|
}
|
||||||
className="mt-1 border-slate-600"
|
className="mt-1 border-slate-600"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-slate-300">
|
<span className="text-sm text-slate-300">
|
||||||
|
|
@ -134,24 +148,28 @@ export default function EcosystemLicenseModal({
|
||||||
<label className="flex items-start gap-3 p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition">
|
<label className="flex items-start gap-3 p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={agreeToTerms}
|
checked={agreeToTerms}
|
||||||
onCheckedChange={(checked) => setAgreeToTerms(checked as boolean)}
|
onCheckedChange={(checked) =>
|
||||||
|
setAgreeToTerms(checked as boolean)
|
||||||
|
}
|
||||||
className="mt-1 border-slate-600"
|
className="mt-1 border-slate-600"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-slate-300">
|
<span className="text-sm text-slate-300">
|
||||||
I agree to the KND-008 Ecosystem License terms and allow AeThex to use my music
|
I agree to the KND-008 Ecosystem License terms and allow AeThex
|
||||||
for non-commercial projects
|
to use my music for non-commercial projects
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label className="flex items-start gap-3 p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition">
|
<label className="flex items-start gap-3 p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={confirmOriginal}
|
checked={confirmOriginal}
|
||||||
onCheckedChange={(checked) => setConfirmOriginal(checked as boolean)}
|
onCheckedChange={(checked) =>
|
||||||
|
setConfirmOriginal(checked as boolean)
|
||||||
|
}
|
||||||
className="mt-1 border-slate-600"
|
className="mt-1 border-slate-600"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-slate-300">
|
<span className="text-sm text-slate-300">
|
||||||
I confirm that this is my original work and I have the right to grant these
|
I confirm that this is my original work and I have the right to
|
||||||
licenses
|
grant these licenses
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -160,7 +178,9 @@ export default function EcosystemLicenseModal({
|
||||||
{allChecked && (
|
{allChecked && (
|
||||||
<div className="flex items-center gap-2 p-3 bg-green-500/10 border border-green-500/30 rounded-lg">
|
<div className="flex items-center gap-2 p-3 bg-green-500/10 border border-green-500/30 rounded-lg">
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||||
<span className="text-sm text-green-400">All terms accepted. Ready to continue.</span>
|
<span className="text-sm text-green-400">
|
||||||
|
All terms accepted. Ready to continue.
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,14 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Search, Star, Clock, DollarSign, CheckCircle, Music } from "lucide-react";
|
import {
|
||||||
|
Search,
|
||||||
|
Star,
|
||||||
|
Clock,
|
||||||
|
DollarSign,
|
||||||
|
CheckCircle,
|
||||||
|
Music,
|
||||||
|
} from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
interface ArtistService {
|
interface ArtistService {
|
||||||
|
|
@ -54,7 +61,9 @@ export default function AudioServicesForHire() {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
// Fetch artists who are for_hire
|
// Fetch artists who are for_hire
|
||||||
const response = await fetch(`/api/ethos/artists?forHire=true&limit=50`);
|
const response = await fetch(
|
||||||
|
`/api/ethos/artists?forHire=true&limit=50`,
|
||||||
|
);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
const artistsData = result.data || result || [];
|
const artistsData = result.data || result || [];
|
||||||
|
|
@ -102,7 +111,7 @@ export default function AudioServicesForHire() {
|
||||||
const matchesSkill =
|
const matchesSkill =
|
||||||
!selectedSkill ||
|
!selectedSkill ||
|
||||||
artist.skills.some((skill) =>
|
artist.skills.some((skill) =>
|
||||||
skill.toLowerCase().includes(selectedSkill.toLowerCase())
|
skill.toLowerCase().includes(selectedSkill.toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
const matchesService =
|
const matchesService =
|
||||||
|
|
@ -116,7 +125,7 @@ export default function AudioServicesForHire() {
|
||||||
|
|
||||||
// Get all unique skills from artists
|
// Get all unique skills from artists
|
||||||
const allSkills = Array.from(
|
const allSkills = Array.from(
|
||||||
new Set(artists.flatMap((artist) => artist.skills))
|
new Set(artists.flatMap((artist) => artist.skills)),
|
||||||
).sort();
|
).sort();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -134,7 +143,10 @@ export default function AudioServicesForHire() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
<Select value={selectedSkill || ""} onValueChange={(val) => setSelectedSkill(val || null)}>
|
<Select
|
||||||
|
value={selectedSkill || ""}
|
||||||
|
onValueChange={(val) => setSelectedSkill(val || null)}
|
||||||
|
>
|
||||||
<SelectTrigger className="bg-slate-800 border-slate-700">
|
<SelectTrigger className="bg-slate-800 border-slate-700">
|
||||||
<SelectValue placeholder="Skill" />
|
<SelectValue placeholder="Skill" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -148,7 +160,10 @@ export default function AudioServicesForHire() {
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Select value={selectedService || ""} onValueChange={(val) => setSelectedService(val || null)}>
|
<Select
|
||||||
|
value={selectedService || ""}
|
||||||
|
onValueChange={(val) => setSelectedService(val || null)}
|
||||||
|
>
|
||||||
<SelectTrigger className="bg-slate-800 border-slate-700">
|
<SelectTrigger className="bg-slate-800 border-slate-700">
|
||||||
<SelectValue placeholder="Service" />
|
<SelectValue placeholder="Service" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -162,7 +177,10 @@ export default function AudioServicesForHire() {
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Select value={minRating.toString()} onValueChange={(val) => setMinRating(Number(val))}>
|
<Select
|
||||||
|
value={minRating.toString()}
|
||||||
|
onValueChange={(val) => setMinRating(Number(val))}
|
||||||
|
>
|
||||||
<SelectTrigger className="bg-slate-800 border-slate-700">
|
<SelectTrigger className="bg-slate-800 border-slate-700">
|
||||||
<SelectValue placeholder="Min Rating" />
|
<SelectValue placeholder="Min Rating" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -191,12 +209,16 @@ export default function AudioServicesForHire() {
|
||||||
|
|
||||||
{/* Results Count */}
|
{/* Results Count */}
|
||||||
<div className="text-sm text-slate-400">
|
<div className="text-sm text-slate-400">
|
||||||
{loading ? "Loading..." : `${filteredArtists.length} artist${filteredArtists.length !== 1 ? "s" : ""} available`}
|
{loading
|
||||||
|
? "Loading..."
|
||||||
|
: `${filteredArtists.length} artist${filteredArtists.length !== 1 ? "s" : ""} available`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Artists Grid */}
|
{/* Artists Grid */}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="text-center py-12 text-slate-400">Loading artists...</div>
|
<div className="text-center py-12 text-slate-400">
|
||||||
|
Loading artists...
|
||||||
|
</div>
|
||||||
) : filteredArtists.length === 0 ? (
|
) : filteredArtists.length === 0 ? (
|
||||||
<div className="text-center py-12 text-slate-400">
|
<div className="text-center py-12 text-slate-400">
|
||||||
No artists found matching your criteria. Try adjusting your filters.
|
No artists found matching your criteria. Try adjusting your filters.
|
||||||
|
|
@ -226,7 +248,9 @@ export default function AudioServicesForHire() {
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<div className="flex items-center gap-1 mt-1">
|
<div className="flex items-center gap-1 mt-1">
|
||||||
<Star className="h-4 w-4 text-yellow-500" />
|
<Star className="h-4 w-4 text-yellow-500" />
|
||||||
<span className="text-sm text-slate-300">{artist.rating.toFixed(1)}</span>
|
<span className="text-sm text-slate-300">
|
||||||
|
{artist.rating.toFixed(1)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -234,7 +258,9 @@ export default function AudioServicesForHire() {
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{/* Bio */}
|
{/* Bio */}
|
||||||
{artist.bio && (
|
{artist.bio && (
|
||||||
<p className="text-sm text-slate-300 line-clamp-2">{artist.bio}</p>
|
<p className="text-sm text-slate-300 line-clamp-2">
|
||||||
|
{artist.bio}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Skills */}
|
{/* Skills */}
|
||||||
|
|
@ -242,7 +268,11 @@ export default function AudioServicesForHire() {
|
||||||
<p className="text-xs font-medium text-slate-400">Skills</p>
|
<p className="text-xs font-medium text-slate-400">Skills</p>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{artist.skills.slice(0, 3).map((skill) => (
|
{artist.skills.slice(0, 3).map((skill) => (
|
||||||
<Badge key={skill} variant="secondary" className="text-xs">
|
<Badge
|
||||||
|
key={skill}
|
||||||
|
variant="secondary"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
{skill}
|
{skill}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ export default function AudioTracksForSale() {
|
||||||
if (selectedLicense) params.append("licenseType", selectedLicense);
|
if (selectedLicense) params.append("licenseType", selectedLicense);
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/ethos/tracks?${params.toString()}&limit=20`
|
`/api/ethos/tracks?${params.toString()}&limit=20`,
|
||||||
);
|
);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const { data } = await response.json();
|
const { data } = await response.json();
|
||||||
|
|
@ -116,7 +116,10 @@ export default function AudioTracksForSale() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
<Select value={selectedGenre || ""} onValueChange={(val) => setSelectedGenre(val || null)}>
|
<Select
|
||||||
|
value={selectedGenre || ""}
|
||||||
|
onValueChange={(val) => setSelectedGenre(val || null)}
|
||||||
|
>
|
||||||
<SelectTrigger className="bg-slate-800 border-slate-700">
|
<SelectTrigger className="bg-slate-800 border-slate-700">
|
||||||
<SelectValue placeholder="Genre" />
|
<SelectValue placeholder="Genre" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -130,7 +133,10 @@ export default function AudioTracksForSale() {
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Select value={selectedLicense || ""} onValueChange={(val) => setSelectedLicense(val || null)}>
|
<Select
|
||||||
|
value={selectedLicense || ""}
|
||||||
|
onValueChange={(val) => setSelectedLicense(val || null)}
|
||||||
|
>
|
||||||
<SelectTrigger className="bg-slate-800 border-slate-700">
|
<SelectTrigger className="bg-slate-800 border-slate-700">
|
||||||
<SelectValue placeholder="License Type" />
|
<SelectValue placeholder="License Type" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -178,7 +184,9 @@ export default function AudioTracksForSale() {
|
||||||
|
|
||||||
{/* Tracks Grid */}
|
{/* Tracks Grid */}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="text-center py-12 text-slate-400">Loading tracks...</div>
|
<div className="text-center py-12 text-slate-400">
|
||||||
|
Loading tracks...
|
||||||
|
</div>
|
||||||
) : sortedTracks.length === 0 ? (
|
) : sortedTracks.length === 0 ? (
|
||||||
<div className="text-center py-12 text-slate-400">
|
<div className="text-center py-12 text-slate-400">
|
||||||
No tracks found. Try adjusting your filters.
|
No tracks found. Try adjusting your filters.
|
||||||
|
|
@ -239,7 +247,9 @@ export default function AudioTracksForSale() {
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Star className="h-4 w-4 text-yellow-500" />
|
<Star className="h-4 w-4 text-yellow-500" />
|
||||||
<span className="text-slate-300">{track.rating || 5.0}</span>
|
<span className="text-slate-300">
|
||||||
|
{track.rating || 5.0}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 text-slate-400">
|
<div className="flex items-center gap-1 text-slate-400">
|
||||||
<Download className="h-4 w-4" />
|
<Download className="h-4 w-4" />
|
||||||
|
|
|
||||||
|
|
@ -224,18 +224,27 @@ export default function Nexus() {
|
||||||
Ethos Guild - Music & Audio Services
|
Ethos Guild - Music & Audio Services
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-purple-200/70 max-w-2xl mx-auto">
|
<p className="text-purple-200/70 max-w-2xl mx-auto">
|
||||||
Discover original tracks and hire verified audio artists for composition, SFX design, and sound engineering. Support independent creators and get high-quality audio for your projects.
|
Discover original tracks and hire verified audio artists for
|
||||||
|
composition, SFX design, and sound engineering. Support
|
||||||
|
independent creators and get high-quality audio for your
|
||||||
|
projects.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabs for Tracks & Services */}
|
{/* Tabs for Tracks & Services */}
|
||||||
<Tabs defaultValue="tracks" className="w-full">
|
<Tabs defaultValue="tracks" className="w-full">
|
||||||
<TabsList className="mb-8 bg-slate-800/50 border border-slate-700">
|
<TabsList className="mb-8 bg-slate-800/50 border border-slate-700">
|
||||||
<TabsTrigger value="tracks" className="flex items-center gap-2">
|
<TabsTrigger
|
||||||
|
value="tracks"
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
<Music className="h-4 w-4" />
|
<Music className="h-4 w-4" />
|
||||||
Tracks for Sale
|
Tracks for Sale
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="artists" className="flex items-center gap-2">
|
<TabsTrigger
|
||||||
|
value="artists"
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
<Users className="h-4 w-4" />
|
<Users className="h-4 w-4" />
|
||||||
Hire Artists
|
Hire Artists
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
|
@ -248,7 +257,9 @@ export default function Nexus() {
|
||||||
Browse Pre-made Music
|
Browse Pre-made Music
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-slate-400 text-sm">
|
<p className="text-slate-400 text-sm">
|
||||||
Find original tracks available under ecosystem licenses (free for non-commercial use) or commercial licenses (for games, films, content).
|
Find original tracks available under ecosystem licenses
|
||||||
|
(free for non-commercial use) or commercial licenses (for
|
||||||
|
games, films, content).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<AudioTracksForSale />
|
<AudioTracksForSale />
|
||||||
|
|
@ -261,7 +272,10 @@ export default function Nexus() {
|
||||||
Hire Verified Artists
|
Hire Verified Artists
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-slate-400 text-sm">
|
<p className="text-slate-400 text-sm">
|
||||||
Work directly with Ethos Guild artists for custom compositions, SFX packs, game scores, and audio production services. Artists set their own prices and maintain full creative control.
|
Work directly with Ethos Guild artists for custom
|
||||||
|
compositions, SFX packs, game scores, and audio production
|
||||||
|
services. Artists set their own prices and maintain full
|
||||||
|
creative control.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<AudioServicesForHire />
|
<AudioServicesForHire />
|
||||||
|
|
@ -277,7 +291,8 @@ export default function Nexus() {
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-sm text-purple-200/70">
|
<CardContent className="text-sm text-purple-200/70">
|
||||||
Artists keep 80% of licensing revenue. AeThex takes 20% to support the platform and help artists grow.
|
Artists keep 80% of licensing revenue. AeThex takes 20% to
|
||||||
|
support the platform and help artists grow.
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
@ -288,7 +303,8 @@ export default function Nexus() {
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-sm text-purple-200/70">
|
<CardContent className="text-sm text-purple-200/70">
|
||||||
Artists retain 100% ownership of their music. License on NEXUS, elsewhere, or both. You decide.
|
Artists retain 100% ownership of their music. License on
|
||||||
|
NEXUS, elsewhere, or both. You decide.
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
@ -299,7 +315,8 @@ export default function Nexus() {
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-sm text-purple-200/70">
|
<CardContent className="text-sm text-purple-200/70">
|
||||||
Build your portfolio in the FOUNDATION community before launching on NEXUS. Get mentorship and feedback from peers.
|
Build your portfolio in the FOUNDATION community before
|
||||||
|
launching on NEXUS. Get mentorship and feedback from peers.
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,14 @@ import {
|
||||||
} from "@/lib/aethex-database-adapter";
|
} from "@/lib/aethex-database-adapter";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
import FourOhFourPage from "@/pages/404";
|
import FourOhFourPage from "@/pages/404";
|
||||||
import { Clock, Rocket, Target, ExternalLink, Award, Music } from "lucide-react";
|
import {
|
||||||
|
Clock,
|
||||||
|
Rocket,
|
||||||
|
Target,
|
||||||
|
ExternalLink,
|
||||||
|
Award,
|
||||||
|
Music,
|
||||||
|
} from "lucide-react";
|
||||||
import { aethexSocialService } from "@/lib/aethex-social-service";
|
import { aethexSocialService } from "@/lib/aethex-social-service";
|
||||||
|
|
||||||
interface ProjectPreview {
|
interface ProjectPreview {
|
||||||
|
|
@ -231,20 +238,21 @@ const ProfilePassport = () => {
|
||||||
authProfile.username.toLowerCase() ===
|
authProfile.username.toLowerCase() ===
|
||||||
resolvedProfile.username.toLowerCase());
|
resolvedProfile.username.toLowerCase());
|
||||||
|
|
||||||
const [achievementList, interestList, projectList, ethos] = await Promise.all([
|
const [achievementList, interestList, projectList, ethos] =
|
||||||
aethexAchievementService
|
await Promise.all([
|
||||||
.getUserAchievements(resolvedId)
|
aethexAchievementService
|
||||||
.catch(() => [] as AethexAchievement[]),
|
.getUserAchievements(resolvedId)
|
||||||
aethexUserService
|
.catch(() => [] as AethexAchievement[]),
|
||||||
.getUserInterests(resolvedId)
|
aethexUserService
|
||||||
.catch(() => [] as string[]),
|
.getUserInterests(resolvedId)
|
||||||
aethexProjectService
|
.catch(() => [] as string[]),
|
||||||
.getUserProjects(resolvedId)
|
aethexProjectService
|
||||||
.catch(() => [] as ProjectPreview[]),
|
.getUserProjects(resolvedId)
|
||||||
fetch(`/api/ethos/artists?id=${resolvedId}`)
|
.catch(() => [] as ProjectPreview[]),
|
||||||
.then(res => res.ok ? res.json() : { tracks: [] })
|
fetch(`/api/ethos/artists?id=${resolvedId}`)
|
||||||
.catch(() => ({ tracks: [] })),
|
.then((res) => (res.ok ? res.json() : { tracks: [] }))
|
||||||
]);
|
.catch(() => ({ tracks: [] })),
|
||||||
|
]);
|
||||||
|
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -428,13 +436,21 @@ const ProfilePassport = () => {
|
||||||
</div>
|
</div>
|
||||||
{ethosProfile.skills && ethosProfile.skills.length > 0 && (
|
{ethosProfile.skills && ethosProfile.skills.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-slate-400 mb-2">Skills</p>
|
<p className="text-xs font-medium text-slate-400 mb-2">
|
||||||
|
Skills
|
||||||
|
</p>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{ethosProfile.skills.slice(0, 5).map((skill: string) => (
|
{ethosProfile.skills
|
||||||
<Badge key={skill} variant="secondary" className="text-xs">
|
.slice(0, 5)
|
||||||
{skill}
|
.map((skill: string) => (
|
||||||
</Badge>
|
<Badge
|
||||||
))}
|
key={skill}
|
||||||
|
variant="secondary"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{skill}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
{ethosProfile.skills.length > 5 && (
|
{ethosProfile.skills.length > 5 && (
|
||||||
<Badge variant="secondary" className="text-xs">
|
<Badge variant="secondary" className="text-xs">
|
||||||
+{ethosProfile.skills.length - 5} more
|
+{ethosProfile.skills.length - 5} more
|
||||||
|
|
@ -454,16 +470,26 @@ const ProfilePassport = () => {
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
{ethosTracks.slice(0, 5).map((track: any) => (
|
{ethosTracks.slice(0, 5).map((track: any) => (
|
||||||
<Card key={track.id} className="border border-slate-800 bg-slate-900/50">
|
<Card
|
||||||
|
key={track.id}
|
||||||
|
className="border border-slate-800 bg-slate-900/50"
|
||||||
|
>
|
||||||
<CardContent className="py-3 px-4 flex items-center justify-between">
|
<CardContent className="py-3 px-4 flex items-center justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-sm font-medium text-white">{track.title}</p>
|
<p className="text-sm font-medium text-white">
|
||||||
|
{track.title}
|
||||||
|
</p>
|
||||||
<div className="flex gap-2 mt-1">
|
<div className="flex gap-2 mt-1">
|
||||||
{track.genre && track.genre.slice(0, 2).map((g: string) => (
|
{track.genre &&
|
||||||
<Badge key={g} variant="secondary" className="text-xs">
|
track.genre.slice(0, 2).map((g: string) => (
|
||||||
{g}
|
<Badge
|
||||||
</Badge>
|
key={g}
|
||||||
))}
|
variant="secondary"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{g}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,13 @@ import { useAuth } from "@/contexts/AuthContext";
|
||||||
import TrackUploadModal from "@/components/ethos/TrackUploadModal";
|
import TrackUploadModal from "@/components/ethos/TrackUploadModal";
|
||||||
import TrackMetadataForm from "@/components/ethos/TrackMetadataForm";
|
import TrackMetadataForm from "@/components/ethos/TrackMetadataForm";
|
||||||
import { ethosStorage, getAudioDuration } from "@/lib/ethos-storage";
|
import { ethosStorage, getAudioDuration } from "@/lib/ethos-storage";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { useAethexToast } from "@/hooks/use-aethex-toast";
|
import { useAethexToast } from "@/hooks/use-aethex-toast";
|
||||||
import { Upload, Music, Settings, CheckCircle, Clock } from "lucide-react";
|
import { Upload, Music, Settings, CheckCircle, Clock } from "lucide-react";
|
||||||
|
|
@ -70,10 +76,12 @@ export default function ArtistSettings() {
|
||||||
const [uploadModalOpen, setUploadModalOpen] = useState(false);
|
const [uploadModalOpen, setUploadModalOpen] = useState(false);
|
||||||
const [currentFile, setCurrentFile] = useState<File | null>(null);
|
const [currentFile, setCurrentFile] = useState<File | null>(null);
|
||||||
const [showMetadataForm, setShowMetadataForm] = useState(false);
|
const [showMetadataForm, setShowMetadataForm] = useState(false);
|
||||||
const [verificationStatus, setVerificationStatus] = useState<VerificationStatus>({
|
const [verificationStatus, setVerificationStatus] =
|
||||||
status: "none",
|
useState<VerificationStatus>({
|
||||||
});
|
status: "none",
|
||||||
const [isSubmittingVerification, setIsSubmittingVerification] = useState(false);
|
});
|
||||||
|
const [isSubmittingVerification, setIsSubmittingVerification] =
|
||||||
|
useState(false);
|
||||||
const [submissionNotes, setSubmissionNotes] = useState("");
|
const [submissionNotes, setSubmissionNotes] = useState("");
|
||||||
const [portfolioLinks, setPortfolioLinks] = useState("");
|
const [portfolioLinks, setPortfolioLinks] = useState("");
|
||||||
const [showLicenseModal, setShowLicenseModal] = useState(false);
|
const [showLicenseModal, setShowLicenseModal] = useState(false);
|
||||||
|
|
@ -323,7 +331,8 @@ export default function ArtistSettings() {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
toast.success({
|
toast.success({
|
||||||
title: "Track uploaded successfully! 🎵",
|
title: "Track uploaded successfully! 🎵",
|
||||||
description: "Your track has been added to your portfolio and is ready to share",
|
description:
|
||||||
|
"Your track has been added to your portfolio and is ready to share",
|
||||||
});
|
});
|
||||||
setShowMetadataForm(false);
|
setShowMetadataForm(false);
|
||||||
setCurrentFile(null);
|
setCurrentFile(null);
|
||||||
|
|
@ -340,7 +349,11 @@ export default function ArtistSettings() {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Layout><div className="py-20 text-center">Loading settings...</div></Layout>;
|
return (
|
||||||
|
<Layout>
|
||||||
|
<div className="py-20 text-center">Loading settings...</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -364,7 +377,9 @@ export default function ArtistSettings() {
|
||||||
{/* Profile Section */}
|
{/* Profile Section */}
|
||||||
<Card className="bg-slate-900/50 border-slate-800">
|
<Card className="bg-slate-900/50 border-slate-800">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-white">Profile Information</CardTitle>
|
<CardTitle className="text-white">
|
||||||
|
Profile Information
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -384,7 +399,10 @@ export default function ArtistSettings() {
|
||||||
<Input
|
<Input
|
||||||
value={profile.portfolio_url || ""}
|
value={profile.portfolio_url || ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setProfile({ ...profile, portfolio_url: e.target.value })
|
setProfile({
|
||||||
|
...profile,
|
||||||
|
portfolio_url: e.target.value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
placeholder="https://yourportfolio.com"
|
placeholder="https://yourportfolio.com"
|
||||||
type="url"
|
type="url"
|
||||||
|
|
@ -411,7 +429,9 @@ export default function ArtistSettings() {
|
||||||
<Card className="bg-slate-900/50 border-slate-800">
|
<Card className="bg-slate-900/50 border-slate-800">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-white">Skills</CardTitle>
|
<CardTitle className="text-white">Skills</CardTitle>
|
||||||
<CardDescription>Select the skills you specialize in</CardDescription>
|
<CardDescription>
|
||||||
|
Select the skills you specialize in
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||||
|
|
@ -436,9 +456,12 @@ export default function ArtistSettings() {
|
||||||
{profile.for_hire && (
|
{profile.for_hire && (
|
||||||
<Card className="bg-slate-900/50 border-slate-800">
|
<Card className="bg-slate-900/50 border-slate-800">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-white">Services & Pricing</CardTitle>
|
<CardTitle className="text-white">
|
||||||
|
Services & Pricing
|
||||||
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Set your prices for custom services. Leave blank if you prefer "Contact for Quote"
|
Set your prices for custom services. Leave blank if you
|
||||||
|
prefer "Contact for Quote"
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
|
|
@ -461,7 +484,9 @@ export default function ArtistSettings() {
|
||||||
className="bg-slate-800 border-slate-700"
|
className="bg-slate-800 border-slate-700"
|
||||||
min="0"
|
min="0"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-400">Original music composition</p>
|
<p className="text-xs text-slate-400">
|
||||||
|
Original music composition
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -482,7 +507,9 @@ export default function ArtistSettings() {
|
||||||
className="bg-slate-800 border-slate-700"
|
className="bg-slate-800 border-slate-700"
|
||||||
min="0"
|
min="0"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-400">Sound effects collection</p>
|
<p className="text-xs text-slate-400">
|
||||||
|
Sound effects collection
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -503,7 +530,9 @@ export default function ArtistSettings() {
|
||||||
className="bg-slate-800 border-slate-700"
|
className="bg-slate-800 border-slate-700"
|
||||||
min="0"
|
min="0"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-400">Complete game/film score</p>
|
<p className="text-xs text-slate-400">
|
||||||
|
Complete game/film score
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -524,26 +553,33 @@ export default function ArtistSettings() {
|
||||||
className="bg-slate-800 border-slate-700"
|
className="bg-slate-800 border-slate-700"
|
||||||
min="0"
|
min="0"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-400">Hourly or daily rate for consulting</p>
|
<p className="text-xs text-slate-400">
|
||||||
|
Hourly or daily rate for consulting
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-white">Turnaround Time (days)</Label>
|
<Label className="text-white">
|
||||||
|
Turnaround Time (days)
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={profile.turnaround_days || ""}
|
value={profile.turnaround_days || ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setProfile({
|
setProfile({
|
||||||
...profile,
|
...profile,
|
||||||
turnaround_days: Number(e.target.value) || undefined,
|
turnaround_days:
|
||||||
|
Number(e.target.value) || undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
placeholder="5"
|
placeholder="5"
|
||||||
className="bg-slate-800 border-slate-700"
|
className="bg-slate-800 border-slate-700"
|
||||||
min="1"
|
min="1"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-400">Typical delivery time for custom work</p>
|
<p className="text-xs text-slate-400">
|
||||||
|
Typical delivery time for custom work
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="flex items-center gap-2 p-3 rounded-lg bg-slate-800/50 border border-slate-700 cursor-pointer">
|
<label className="flex items-center gap-2 p-3 rounded-lg bg-slate-800/50 border border-slate-700 cursor-pointer">
|
||||||
|
|
@ -561,7 +597,8 @@ export default function ArtistSettings() {
|
||||||
className="border-slate-600"
|
className="border-slate-600"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-slate-300">
|
<span className="text-sm text-slate-300">
|
||||||
High-value projects (Enterprise clients): "Contact for Quote"
|
High-value projects (Enterprise clients): "Contact for
|
||||||
|
Quote"
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -575,7 +612,9 @@ export default function ArtistSettings() {
|
||||||
<Music className="h-5 w-5" />
|
<Music className="h-5 w-5" />
|
||||||
Upload Track
|
Upload Track
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>Add a new track to your portfolio</CardDescription>
|
<CardDescription>
|
||||||
|
Add a new track to your portfolio
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -604,10 +643,12 @@ export default function ArtistSettings() {
|
||||||
<div className="p-4 bg-green-500/10 border border-green-500/20 rounded-lg flex items-start gap-3">
|
<div className="p-4 bg-green-500/10 border border-green-500/20 rounded-lg flex items-start gap-3">
|
||||||
<CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0 mt-0.5" />
|
<CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-green-400">Verified Artist</p>
|
<p className="font-semibold text-green-400">
|
||||||
|
Verified Artist
|
||||||
|
</p>
|
||||||
<p className="text-sm text-green-300 mt-1">
|
<p className="text-sm text-green-300 mt-1">
|
||||||
You are a verified Ethos Guild artist. You can upload tracks and accept
|
You are a verified Ethos Guild artist. You can upload
|
||||||
commercial licensing requests.
|
tracks and accept commercial licensing requests.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -615,35 +656,44 @@ export default function ArtistSettings() {
|
||||||
<div className="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg flex items-start gap-3">
|
<div className="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg flex items-start gap-3">
|
||||||
<Clock className="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
|
<Clock className="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-yellow-400">Pending Review</p>
|
<p className="font-semibold text-yellow-400">
|
||||||
|
Pending Review
|
||||||
|
</p>
|
||||||
<p className="text-sm text-yellow-300 mt-1">
|
<p className="text-sm text-yellow-300 mt-1">
|
||||||
Your verification request is under review. We'll email you when there's an
|
Your verification request is under review. We'll email
|
||||||
update.
|
you when there's an update.
|
||||||
</p>
|
</p>
|
||||||
{verificationStatus.submitted_at && (
|
{verificationStatus.submitted_at && (
|
||||||
<p className="text-xs text-yellow-300/70 mt-2">
|
<p className="text-xs text-yellow-300/70 mt-2">
|
||||||
Submitted:{" "}
|
Submitted:{" "}
|
||||||
{new Date(verificationStatus.submitted_at).toLocaleDateString()}
|
{new Date(
|
||||||
|
verificationStatus.submitted_at,
|
||||||
|
).toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : verificationStatus.status === "rejected" ? (
|
) : verificationStatus.status === "rejected" ? (
|
||||||
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
|
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
|
||||||
<p className="font-semibold text-red-400">Application Rejected</p>
|
<p className="font-semibold text-red-400">
|
||||||
|
Application Rejected
|
||||||
|
</p>
|
||||||
{verificationStatus.rejection_reason && (
|
{verificationStatus.rejection_reason && (
|
||||||
<p className="text-sm text-red-300 mt-2">
|
<p className="text-sm text-red-300 mt-2">
|
||||||
{verificationStatus.rejection_reason}
|
{verificationStatus.rejection_reason}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p className="text-sm text-red-300 mt-2">
|
<p className="text-sm text-red-300 mt-2">
|
||||||
You can resubmit with updates to your portfolio or qualifications.
|
You can resubmit with updates to your portfolio or
|
||||||
|
qualifications.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-white">Application Notes (optional)</Label>
|
<Label className="text-white">
|
||||||
|
Application Notes (optional)
|
||||||
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={submissionNotes}
|
value={submissionNotes}
|
||||||
onChange={(e) => setSubmissionNotes(e.target.value)}
|
onChange={(e) => setSubmissionNotes(e.target.value)}
|
||||||
|
|
@ -653,7 +703,9 @@ export default function ArtistSettings() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-white">Portfolio Links (one per line)</Label>
|
<Label className="text-white">
|
||||||
|
Portfolio Links (one per line)
|
||||||
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={portfolioLinks}
|
value={portfolioLinks}
|
||||||
onChange={(e) => setPortfolioLinks(e.target.value)}
|
onChange={(e) => setPortfolioLinks(e.target.value)}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ Ethos Guild is a music production and licensing ecosystem within AeThex. Artists
|
||||||
## Completed Features
|
## Completed Features
|
||||||
|
|
||||||
### Phase 1: Artist Verification Workflow ✅
|
### Phase 1: Artist Verification Workflow ✅
|
||||||
|
|
||||||
- **Admin Dashboard**: `/admin` → "Ethos Verification" tab
|
- **Admin Dashboard**: `/admin` → "Ethos Verification" tab
|
||||||
- **Artist Submission**: `/ethos/settings` → Verification form
|
- **Artist Submission**: `/ethos/settings` → Verification form
|
||||||
- **Verification Process**: Manual review by admins with approval/rejection
|
- **Verification Process**: Manual review by admins with approval/rejection
|
||||||
|
|
@ -14,6 +15,7 @@ Ethos Guild is a music production and licensing ecosystem within AeThex. Artists
|
||||||
- **Database**: `ethos_verification_requests` and `ethos_verification_audit_log` tables
|
- **Database**: `ethos_verification_requests` and `ethos_verification_audit_log` tables
|
||||||
|
|
||||||
### Phase 2: Supabase Storage Integration ✅
|
### Phase 2: Supabase Storage Integration ✅
|
||||||
|
|
||||||
- **Track Upload**: Audio files stored in `ethos-tracks` bucket
|
- **Track Upload**: Audio files stored in `ethos-tracks` bucket
|
||||||
- **Public Access**: Tracks are publicly readable for streaming
|
- **Public Access**: Tracks are publicly readable for streaming
|
||||||
- **User Isolation**: Each user can only upload to their own folder
|
- **User Isolation**: Each user can only upload to their own folder
|
||||||
|
|
@ -21,18 +23,21 @@ Ethos Guild is a music production and licensing ecosystem within AeThex. Artists
|
||||||
- **File Management**: Upload, download, delete operations supported
|
- **File Management**: Upload, download, delete operations supported
|
||||||
|
|
||||||
### Phase 3: Email Notifications ✅
|
### Phase 3: Email Notifications ✅
|
||||||
|
|
||||||
- **SMTP Configuration**: Hostinger SMTP (smtp.hostinger.com:465)
|
- **SMTP Configuration**: Hostinger SMTP (smtp.hostinger.com:465)
|
||||||
- **Templates**: Verification, licensing, and status notifications
|
- **Templates**: Verification, licensing, and status notifications
|
||||||
- **Delivery**: Reliable email delivery via Nodemailer
|
- **Delivery**: Reliable email delivery via Nodemailer
|
||||||
- **Async Processing**: Non-blocking email sending
|
- **Async Processing**: Non-blocking email sending
|
||||||
|
|
||||||
### Phase 4: Ecosystem License Agreement ✅
|
### Phase 4: Ecosystem License Agreement ✅
|
||||||
|
|
||||||
- **Modal Interface**: Click-wrap agreement on first track upload
|
- **Modal Interface**: Click-wrap agreement on first track upload
|
||||||
- **License Tracking**: `ethos_ecosystem_licenses` table
|
- **License Tracking**: `ethos_ecosystem_licenses` table
|
||||||
- **Track Linking**: Accepted licenses linked to specific tracks
|
- **Track Linking**: Accepted licenses linked to specific tracks
|
||||||
- **Re-acceptance**: Artists can accept license once
|
- **Re-acceptance**: Artists can accept license once
|
||||||
|
|
||||||
### Phase 5: Artist Services & Pricing ✅
|
### Phase 5: Artist Services & Pricing ✅
|
||||||
|
|
||||||
- **Flexible Pricing**: JSON-based `price_list` structure
|
- **Flexible Pricing**: JSON-based `price_list` structure
|
||||||
- **Service Types**: Custom tracks, SFX packs, full scores, day rates
|
- **Service Types**: Custom tracks, SFX packs, full scores, day rates
|
||||||
- **For Hire Status**: Boolean flag to show in marketplace
|
- **For Hire Status**: Boolean flag to show in marketplace
|
||||||
|
|
@ -40,6 +45,7 @@ Ethos Guild is a music production and licensing ecosystem within AeThex. Artists
|
||||||
- **Contact System**: Service request form with commission tracking
|
- **Contact System**: Service request form with commission tracking
|
||||||
|
|
||||||
### Phase 6: NEXUS Marketplace Integration ✅
|
### Phase 6: NEXUS Marketplace Integration ✅
|
||||||
|
|
||||||
- **Two Components**: AudioTracksForSale and AudioServicesForHire
|
- **Two Components**: AudioTracksForSale and AudioServicesForHire
|
||||||
- **Artist Directory**: Filter by skills, ratings, services
|
- **Artist Directory**: Filter by skills, ratings, services
|
||||||
- **Track Library**: Filter by genre, license type, price
|
- **Track Library**: Filter by genre, license type, price
|
||||||
|
|
@ -47,6 +53,7 @@ Ethos Guild is a music production and licensing ecosystem within AeThex. Artists
|
||||||
- **Profile Integration**: Links to artist profiles and portfolio
|
- **Profile Integration**: Links to artist profiles and portfolio
|
||||||
|
|
||||||
### Phase 7: Artist Portfolio ✅
|
### Phase 7: Artist Portfolio ✅
|
||||||
|
|
||||||
- **Route**: `/passport/me` (personal) and `/passport/:username` (public)
|
- **Route**: `/passport/me` (personal) and `/passport/:username` (public)
|
||||||
- **Sections**: Ethos Guild info, tracks, skills, verification status
|
- **Sections**: Ethos Guild info, tracks, skills, verification status
|
||||||
- **Self View**: Edit link to settings for own profile
|
- **Self View**: Edit link to settings for own profile
|
||||||
|
|
@ -56,6 +63,7 @@ Ethos Guild is a music production and licensing ecosystem within AeThex. Artists
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### Artist Management
|
### Artist Management
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/ethos/artists?id=<id> - Get single artist
|
GET /api/ethos/artists?id=<id> - Get single artist
|
||||||
GET /api/ethos/artists?for_hire=true - List artists available for hire
|
GET /api/ethos/artists?for_hire=true - List artists available for hire
|
||||||
|
|
@ -64,11 +72,13 @@ PUT /api/ethos/artists - Update artist profile
|
||||||
```
|
```
|
||||||
|
|
||||||
### Artist Services
|
### Artist Services
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/ethos/artist-services/:artist_id - Get artist's service pricing
|
GET /api/ethos/artist-services/:artist_id - Get artist's service pricing
|
||||||
```
|
```
|
||||||
|
|
||||||
### Service Requests
|
### Service Requests
|
||||||
|
|
||||||
```
|
```
|
||||||
POST /api/ethos/service-requests - Create service request
|
POST /api/ethos/service-requests - Create service request
|
||||||
GET /api/ethos/service-requests?artist_id=<id> - List requests for artist
|
GET /api/ethos/service-requests?artist_id=<id> - List requests for artist
|
||||||
|
|
@ -76,18 +86,21 @@ PUT /api/ethos/service-requests/:id - Update request status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tracks
|
### Tracks
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/ethos/tracks - List published tracks
|
GET /api/ethos/tracks - List published tracks
|
||||||
POST /api/ethos/tracks - Upload new track (with auto-license linking)
|
POST /api/ethos/tracks - Upload new track (with auto-license linking)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Verification
|
### Verification
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/ethos/verification - List verification requests (admin)
|
GET /api/ethos/verification - List verification requests (admin)
|
||||||
POST /api/ethos/verification - Submit or manage verification
|
POST /api/ethos/verification - Submit or manage verification
|
||||||
```
|
```
|
||||||
|
|
||||||
### Licensing
|
### Licensing
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/ethos/licensing-agreements - List agreements
|
GET /api/ethos/licensing-agreements - List agreements
|
||||||
POST /api/ethos/licensing-notifications - Send notifications
|
POST /api/ethos/licensing-notifications - Send notifications
|
||||||
|
|
@ -98,6 +111,7 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
### Main Tables
|
### Main Tables
|
||||||
|
|
||||||
**ethos_artist_profiles**
|
**ethos_artist_profiles**
|
||||||
|
|
||||||
- `user_id` (PK): References user_profiles
|
- `user_id` (PK): References user_profiles
|
||||||
- `for_hire`: Boolean flag for marketplace visibility
|
- `for_hire`: Boolean flag for marketplace visibility
|
||||||
- `verified`: Verification status
|
- `verified`: Verification status
|
||||||
|
|
@ -107,6 +121,7 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
- RLS: Users see all, own updates only
|
- RLS: Users see all, own updates only
|
||||||
|
|
||||||
**ethos_tracks**
|
**ethos_tracks**
|
||||||
|
|
||||||
- `id` (PK): UUID
|
- `id` (PK): UUID
|
||||||
- `user_id`: Artist who uploaded
|
- `user_id`: Artist who uploaded
|
||||||
- `title`, `description`: Track info
|
- `title`, `description`: Track info
|
||||||
|
|
@ -118,6 +133,7 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
- RLS: Public read, user write/delete own
|
- RLS: Public read, user write/delete own
|
||||||
|
|
||||||
**ethos_ecosystem_licenses**
|
**ethos_ecosystem_licenses**
|
||||||
|
|
||||||
- `id` (PK): UUID
|
- `id` (PK): UUID
|
||||||
- `track_id`: Which track accepted license
|
- `track_id`: Which track accepted license
|
||||||
- `artist_id`: Which artist accepted
|
- `artist_id`: Which artist accepted
|
||||||
|
|
@ -125,6 +141,7 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
- RLS: User sees own, admins see all
|
- RLS: User sees own, admins see all
|
||||||
|
|
||||||
**ethos_verification_requests**
|
**ethos_verification_requests**
|
||||||
|
|
||||||
- `id` (PK): UUID
|
- `id` (PK): UUID
|
||||||
- `user_id`: Artist requesting verification
|
- `user_id`: Artist requesting verification
|
||||||
- `status`: "pending", "approved", "rejected"
|
- `status`: "pending", "approved", "rejected"
|
||||||
|
|
@ -133,6 +150,7 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
- RLS: Artists see own, admins see all
|
- RLS: Artists see own, admins see all
|
||||||
|
|
||||||
**ethos_service_requests**
|
**ethos_service_requests**
|
||||||
|
|
||||||
- `id` (PK): UUID
|
- `id` (PK): UUID
|
||||||
- `artist_id`: Requested artist
|
- `artist_id`: Requested artist
|
||||||
- `requester_id`: Client requesting service
|
- `requester_id`: Client requesting service
|
||||||
|
|
@ -142,7 +160,9 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
- RLS: Both parties can view, artist updates
|
- RLS: Both parties can view, artist updates
|
||||||
|
|
||||||
### Storage Bucket
|
### Storage Bucket
|
||||||
|
|
||||||
**ethos-tracks** (Public)
|
**ethos-tracks** (Public)
|
||||||
|
|
||||||
- Path: `/{user_id}/{track_id}/audio.mp3`
|
- Path: `/{user_id}/{track_id}/audio.mp3`
|
||||||
- RLS: Authenticated users can upload to own folder, public read, user delete own
|
- RLS: Authenticated users can upload to own folder, public read, user delete own
|
||||||
- Policy: Users isolated to their own folder, public streaming access
|
- Policy: Users isolated to their own folder, public streaming access
|
||||||
|
|
@ -150,6 +170,7 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
## User Flows
|
## User Flows
|
||||||
|
|
||||||
### Artist Upload Flow
|
### Artist Upload Flow
|
||||||
|
|
||||||
1. Artist goes to `/ethos/settings`
|
1. Artist goes to `/ethos/settings`
|
||||||
2. Clicks "Upload Track" button
|
2. Clicks "Upload Track" button
|
||||||
3. Selects audio file
|
3. Selects audio file
|
||||||
|
|
@ -164,6 +185,7 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
12. Success toast shown, track appears in library
|
12. Success toast shown, track appears in library
|
||||||
|
|
||||||
### Verification Flow
|
### Verification Flow
|
||||||
|
|
||||||
1. Artist goes to `/ethos/settings` → "Request Verification"
|
1. Artist goes to `/ethos/settings` → "Request Verification"
|
||||||
2. Fills form: bio, skills, portfolio links, submission notes
|
2. Fills form: bio, skills, portfolio links, submission notes
|
||||||
3. POST to `/api/ethos/verification` with action: "submit"
|
3. POST to `/api/ethos/verification` with action: "submit"
|
||||||
|
|
@ -177,6 +199,7 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
11. Artist can see status on settings page
|
11. Artist can see status on settings page
|
||||||
|
|
||||||
### Marketplace Discovery Flow
|
### Marketplace Discovery Flow
|
||||||
|
|
||||||
1. User goes to `/nexus`
|
1. User goes to `/nexus`
|
||||||
2. Clicks "Services for Hire" tab
|
2. Clicks "Services for Hire" tab
|
||||||
3. Component fetches: `GET /api/ethos/artists?forHire=true&limit=50`
|
3. Component fetches: `GET /api/ethos/artists?forHire=true&limit=50`
|
||||||
|
|
@ -196,6 +219,7 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
12. Artist can accept/decline in dashboard
|
12. Artist can accept/decline in dashboard
|
||||||
|
|
||||||
### Artist Portfolio View
|
### Artist Portfolio View
|
||||||
|
|
||||||
1. Artist goes to `/passport/me` (personal portfolio)
|
1. Artist goes to `/passport/me` (personal portfolio)
|
||||||
2. **Ethos Guild Section Shows**:
|
2. **Ethos Guild Section Shows**:
|
||||||
- Verified Artist badge (if applicable)
|
- Verified Artist badge (if applicable)
|
||||||
|
|
@ -214,6 +238,7 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
## Deployment Checklist
|
## Deployment Checklist
|
||||||
|
|
||||||
### Database
|
### Database
|
||||||
|
|
||||||
- [ ] Apply migration: `20250206_add_ethos_guild.sql`
|
- [ ] Apply migration: `20250206_add_ethos_guild.sql`
|
||||||
- [ ] Apply migration: `20250210_add_ethos_artist_verification.sql`
|
- [ ] Apply migration: `20250210_add_ethos_artist_verification.sql`
|
||||||
- [ ] Apply migration: `20250210_setup_ethos_storage.sql` (read-only in SQL)
|
- [ ] Apply migration: `20250210_setup_ethos_storage.sql` (read-only in SQL)
|
||||||
|
|
@ -221,12 +246,14 @@ POST /api/ethos/licensing-notifications - Send notifications
|
||||||
- [ ] Apply migration: `20250212_add_ethos_service_requests.sql`
|
- [ ] Apply migration: `20250212_add_ethos_service_requests.sql`
|
||||||
|
|
||||||
### Storage Setup
|
### Storage Setup
|
||||||
|
|
||||||
- [ ] Create Supabase Storage bucket: "ethos-tracks"
|
- [ ] Create Supabase Storage bucket: "ethos-tracks"
|
||||||
- [ ] Make bucket PUBLIC
|
- [ ] Make bucket PUBLIC
|
||||||
- [ ] Apply RLS policies (see migration comments)
|
- [ ] Apply RLS policies (see migration comments)
|
||||||
- [ ] Test upload from browser
|
- [ ] Test upload from browser
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
```
|
```
|
||||||
VITE_SUPABASE_URL=https://kmdeisowhtsalsekkzqd.supabase.co
|
VITE_SUPABASE_URL=https://kmdeisowhtsalsekkzqd.supabase.co
|
||||||
VITE_SUPABASE_ANON_KEY=<your-anon-key>
|
VITE_SUPABASE_ANON_KEY=<your-anon-key>
|
||||||
|
|
@ -239,6 +266,7 @@ SMTP_FROM_EMAIL=no-reply@aethex.tech
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing Steps
|
### Testing Steps
|
||||||
|
|
||||||
1. [ ] Create user account
|
1. [ ] Create user account
|
||||||
2. [ ] Go to `/ethos/settings`
|
2. [ ] Go to `/ethos/settings`
|
||||||
3. [ ] Upload track → Accept license → Fill metadata → Upload to storage
|
3. [ ] Upload track → Accept license → Fill metadata → Upload to storage
|
||||||
|
|
@ -257,24 +285,29 @@ SMTP_FROM_EMAIL=no-reply@aethex.tech
|
||||||
## Technical Details
|
## Technical Details
|
||||||
|
|
||||||
### License Linking Logic
|
### License Linking Logic
|
||||||
|
|
||||||
When a track is uploaded with `license_type: "ecosystem"`:
|
When a track is uploaded with `license_type: "ecosystem"`:
|
||||||
|
|
||||||
1. Track record created in `ethos_tracks`
|
1. Track record created in `ethos_tracks`
|
||||||
2. Immediately after insert, code creates record in `ethos_ecosystem_licenses`
|
2. Immediately after insert, code creates record in `ethos_ecosystem_licenses`
|
||||||
3. Links: track_id, artist_id, accepted_at (current timestamp)
|
3. Links: track_id, artist_id, accepted_at (current timestamp)
|
||||||
4. This establishes the relationship between license agreement and track
|
4. This establishes the relationship between license agreement and track
|
||||||
|
|
||||||
### Storage Path Format
|
### Storage Path Format
|
||||||
|
|
||||||
```
|
```
|
||||||
ethos-tracks/
|
ethos-tracks/
|
||||||
{user_id}/
|
{user_id}/
|
||||||
{track_id}/
|
{track_id}/
|
||||||
audio.mp3
|
audio.mp3
|
||||||
```
|
```
|
||||||
|
|
||||||
- User ID isolates folders for RLS
|
- User ID isolates folders for RLS
|
||||||
- Track ID groups related files
|
- Track ID groups related files
|
||||||
- Flat structure easy to manage
|
- Flat structure easy to manage
|
||||||
|
|
||||||
### Service Pricing Structure
|
### Service Pricing Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"price_list": {
|
"price_list": {
|
||||||
|
|
@ -286,11 +319,13 @@ ethos-tracks/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Flexible JSON for future service types
|
- Flexible JSON for future service types
|
||||||
- `null` values mean service not available
|
- `null` values mean service not available
|
||||||
- Boolean flag for custom quotes
|
- Boolean flag for custom quotes
|
||||||
|
|
||||||
### Verification Workflow
|
### Verification Workflow
|
||||||
|
|
||||||
```
|
```
|
||||||
pending → admin review → approved/rejected
|
pending → admin review → approved/rejected
|
||||||
↓ ↓
|
↓ ↓
|
||||||
|
|
@ -314,6 +349,7 @@ pending → admin review → approved/rejected
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
For issues or questions about Ethos Guild:
|
For issues or questions about Ethos Guild:
|
||||||
|
|
||||||
1. Check `/docs` section for tutorials
|
1. Check `/docs` section for tutorials
|
||||||
2. Review `/ethos/library` for example tracks
|
2. Review `/ethos/library` for example tracks
|
||||||
3. See `/admin` → "Ethos Verification" for status
|
3. See `/admin` → "Ethos Verification" for status
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue