brackt/app/components/ui/AvatarEditor.tsx

137 lines
4.2 KiB
TypeScript
Raw Normal View History

import { useMemo, useState } from "react";
import { Save, User } from "lucide-react";
import { getDisplayFlagConfig } from "~/lib/avatar-data";
import type { AvatarData, FlagConfig, RawFlagConfig } from "~/lib/flag-types";
import { Button } from "~/components/ui/button";
import { FlagBuilder } from "~/components/ui/FlagBuilder";
import { FlagSvg } from "~/components/ui/FlagSvg";
import { AvatarUploader } from "~/components/ui/AvatarUploader";
import { cloudinaryAvatarUrl } from "~/lib/cloudinary-url";
import { cn } from "~/lib/utils";
interface AvatarEditorProps {
id: string;
flagConfig?: RawFlagConfig | null;
uploadedPhotoUrl?: string | null;
uploadUrl: string;
isTeam?: boolean;
currentAvatarType?: string | null;
ownerAvatarData?: AvatarData | null;
flagIntent?: string;
removePhotoIntent?: string;
useOwnerAvatarIntent?: string;
}
type Mode = "flag" | "upload" | "owner";
export function AvatarEditor({
id,
flagConfig,
uploadedPhotoUrl,
uploadUrl,
isTeam = false,
currentAvatarType,
ownerAvatarData,
flagIntent = "update-avatar-flag",
removePhotoIntent = "remove-avatar-photo",
useOwnerAvatarIntent = "use-owner-avatar",
}: AvatarEditorProps) {
const initialFlag = useMemo(() => getDisplayFlagConfig(id, flagConfig), [flagConfig, id]);
const [config, setConfig] = useState<FlagConfig>(initialFlag);
const showOwnerTab = isTeam && ownerAvatarData !== null && ownerAvatarData !== undefined;
const initialMode: Mode =
currentAvatarType === "owner" && showOwnerTab ? "owner" :
currentAvatarType === "uploaded" ? "upload" :
"flag";
const [mode, setMode] = useState<Mode>(initialMode);
const tabs = [
{ key: "flag" as const, label: "Flag" },
{ key: "upload" as const, label: "Upload Photo" },
...(showOwnerTab ? [{ key: "owner" as const, label: "My Avatar" }] : []),
];
return (
<div className="space-y-4">
<div className="flex overflow-hidden rounded-lg border border-border">
{tabs.map((tab) => (
<button
key={tab.key}
type="button"
onClick={() => setMode(tab.key)}
className={cn(
"flex-1 py-2 text-sm font-medium transition-colors",
mode === tab.key
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-accent"
)}
>
{tab.label}
</button>
))}
</div>
{mode === "flag" && (
<form method="post" className="space-y-4">
<input type="hidden" name="intent" value={flagIntent} />
<input type="hidden" name="flagConfig" value={JSON.stringify(config)} />
<FlagBuilder value={config} seed={id} onChange={setConfig} />
<Button type="submit" className="w-full">
<Save className="mr-2 h-4 w-4" />
Save flag
</Button>
</form>
)}
{mode === "upload" && (
<AvatarUploader
uploadUrl={uploadUrl}
teamId={isTeam ? id : undefined}
savedPhotoUrl={uploadedPhotoUrl}
removePhotoIntent={removePhotoIntent}
/>
)}
{mode === "owner" && ownerAvatarData && (
<form method="post" className="space-y-4">
<input type="hidden" name="intent" value={useOwnerAvatarIntent} />
<div className="flex flex-col items-center gap-3 py-2">
<OwnerAvatarPreview avatarData={ownerAvatarData} />
<p className="text-sm text-muted-foreground">Your team will use your current profile avatar.</p>
</div>
<Button type="submit" className="w-full">
<User className="mr-2 h-4 w-4" />
Use my avatar
</Button>
</form>
)}
</div>
);
}
function OwnerAvatarPreview({ avatarData }: { avatarData: AvatarData }) {
if (avatarData.type === "image") {
return (
<img
src={cloudinaryAvatarUrl(avatarData.url, 64)}
alt="Your avatar"
className="h-16 w-16 rounded-full object-cover ring-2 ring-border"
/>
);
}
if (avatarData.type === "flag") {
return (
<FlagSvg
config={avatarData.config}
size={64}
title="Your avatar"
className="h-16 w-16 rounded-full ring-2 ring-border"
/>
);
}
return null;
}