Add support for company logos, including schema adjustments, validation, assets handling, and UI display enhancements.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
import { companiesApi } from "../api/companies";
|
||||
import { accessApi } from "../api/access";
|
||||
import { assetsApi } from "../api/assets";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Settings, Check } from "lucide-react";
|
||||
@@ -34,6 +35,8 @@ export function CompanySettings() {
|
||||
const [companyName, setCompanyName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [brandColor, setBrandColor] = useState("");
|
||||
const [logoUrl, setLogoUrl] = useState("");
|
||||
const [logoUploadError, setLogoUploadError] = useState<string | null>(null);
|
||||
|
||||
// Sync local state from selected company
|
||||
useEffect(() => {
|
||||
@@ -41,6 +44,7 @@ export function CompanySettings() {
|
||||
setCompanyName(selectedCompany.name);
|
||||
setDescription(selectedCompany.description ?? "");
|
||||
setBrandColor(selectedCompany.brandColor ?? "");
|
||||
setLogoUrl(selectedCompany.logoUrl ?? "");
|
||||
}, [selectedCompany]);
|
||||
|
||||
const [inviteError, setInviteError] = useState<string | null>(null);
|
||||
@@ -130,6 +134,46 @@ export function CompanySettings() {
|
||||
}
|
||||
});
|
||||
|
||||
const syncLogoState = (nextLogoUrl: string | null) => {
|
||||
setLogoUrl(nextLogoUrl ?? "");
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.companies.all });
|
||||
};
|
||||
|
||||
const logoUploadMutation = useMutation({
|
||||
mutationFn: (file: File) =>
|
||||
assetsApi
|
||||
.uploadImage(selectedCompanyId!, file, "companies")
|
||||
.then((asset) => companiesApi.update(selectedCompanyId!, { logoUrl: asset.contentPath })),
|
||||
onSuccess: (company) => {
|
||||
syncLogoState(company.logoUrl);
|
||||
setLogoUploadError(null);
|
||||
}
|
||||
});
|
||||
|
||||
const clearLogoMutation = useMutation({
|
||||
mutationFn: () => companiesApi.update(selectedCompanyId!, { logoUrl: null }),
|
||||
onSuccess: (company) => {
|
||||
setLogoUploadError(null);
|
||||
syncLogoState(company.logoUrl);
|
||||
}
|
||||
});
|
||||
|
||||
function handleLogoFileChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
const file = event.target.files?.[0] ?? null;
|
||||
event.currentTarget.value = "";
|
||||
if (!file) return;
|
||||
if (file.size >= 100 * 1024) {
|
||||
setLogoUploadError("Logo image must be smaller than 100 KB.");
|
||||
return;
|
||||
}
|
||||
setLogoUploadError(null);
|
||||
logoUploadMutation.mutate(file);
|
||||
}
|
||||
|
||||
function handleClearLogo() {
|
||||
clearLogoMutation.mutate();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setInviteError(null);
|
||||
setInviteSnippet(null);
|
||||
@@ -226,11 +270,53 @@ export function CompanySettings() {
|
||||
<div className="shrink-0">
|
||||
<CompanyPatternIcon
|
||||
companyName={companyName || selectedCompany.name}
|
||||
logoUrl={logoUrl || null}
|
||||
brandColor={brandColor || null}
|
||||
className="rounded-[14px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="flex-1 space-y-3">
|
||||
<Field
|
||||
label="Logo"
|
||||
hint="Upload a logo image to replace the generated icon. Maximum size: 100 KB."
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*,image/svg+xml"
|
||||
onChange={handleLogoFileChange}
|
||||
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none file:mr-4 file:rounded-md file:border-0 file:bg-muted file:px-2.5 file:py-1 file:text-xs"
|
||||
/>
|
||||
{logoUrl && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleClearLogo}
|
||||
disabled={clearLogoMutation.isPending}
|
||||
>
|
||||
{clearLogoMutation.isPending ? "Removing..." : "Remove logo"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{(logoUploadMutation.isError || logoUploadError) && (
|
||||
<span className="text-xs text-destructive">
|
||||
{logoUploadError ??
|
||||
(logoUploadMutation.error instanceof Error
|
||||
? logoUploadMutation.error.message
|
||||
: "Logo upload failed")}
|
||||
</span>
|
||||
)}
|
||||
{clearLogoMutation.isError && (
|
||||
<span className="text-xs text-destructive">
|
||||
{clearLogoMutation.error.message}
|
||||
</span>
|
||||
)}
|
||||
{logoUploadMutation.isPending && (
|
||||
<span className="text-xs text-muted-foreground">Uploading logo...</span>
|
||||
)}
|
||||
</div>
|
||||
</Field>
|
||||
<Field
|
||||
label="Brand color"
|
||||
hint="Sets the hue for the company icon. Leave empty for auto-generated color."
|
||||
@@ -286,9 +372,7 @@ export function CompanySettings() {
|
||||
)}
|
||||
{generalMutation.isError && (
|
||||
<span className="text-xs text-destructive">
|
||||
{generalMutation.error instanceof Error
|
||||
? generalMutation.error.message
|
||||
: "Failed to save"}
|
||||
{generalMutation.error.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user