Fix web typecheck errors across core feature modules
This commit is contained in:
@@ -56,7 +56,7 @@ export default function AddClient() {
|
||||
|
||||
const { mutateAsync: createBusiness, isPending: isCreatingBusiness } = useCreateBusiness(dataConnect);
|
||||
const { mutateAsync: createHub, isPending: isCreatingHub } = useCreateTeamHub(dataConnect);
|
||||
const { mutateAsync: createTeam, isPending: isCreatingTeam } = useCreateTeam(dataConnect);
|
||||
const { mutateAsync: createTeam } = useCreateTeam(dataConnect);
|
||||
|
||||
const handleChange = (field: string, value: any) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
|
||||
@@ -28,9 +28,7 @@ import {
|
||||
useCreateCustomRateCard,
|
||||
useUpdateCustomRateCard,
|
||||
useDeleteCustomRateCard,
|
||||
useCreateVendorRate,
|
||||
useUpdateVendorRate,
|
||||
useDeleteVendorRate,
|
||||
useGetVendorByUserId
|
||||
} from "@/dataconnect-generated/react";
|
||||
import RateCardModal from "./components/RateCardModal";
|
||||
@@ -111,9 +109,7 @@ function VendorCompanyPricebookView({
|
||||
const { mutate: updateCustomRateCard } = useUpdateCustomRateCard();
|
||||
const { mutate: deleteCustomRateCard } = useDeleteCustomRateCard();
|
||||
|
||||
const { mutate: createVendorRate } = useCreateVendorRate();
|
||||
const { mutate: updateVendorRate } = useUpdateVendorRate();
|
||||
const { mutate: deleteVendorRate } = useDeleteVendorRate();
|
||||
|
||||
const handleUpdateVendorRate = (vars: any) => {
|
||||
updateVendorRate(vars, {
|
||||
@@ -146,8 +142,13 @@ function VendorCompanyPricebookView({
|
||||
}, [vendorRates, vendorName]);
|
||||
|
||||
const CATEGORIES = useMemo(() => {
|
||||
const cats = new Set(vendorRates.map(r => r.category).filter(Boolean));
|
||||
return Array.from(cats);
|
||||
const categories = vendorRates.reduce<string[]>((acc, r) => {
|
||||
if (r.category) {
|
||||
acc.push(String(r.category));
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
return Array.from(new Set(categories));
|
||||
}, [vendorRates]);
|
||||
|
||||
const handleSaveRateCard = (cardData: any) => {
|
||||
@@ -233,7 +234,6 @@ function VendorCompanyPricebookView({
|
||||
|
||||
return rates.map((r: any) => {
|
||||
const parsed = parseRoleName(r.roleName || "");
|
||||
const position = parsed.position;
|
||||
|
||||
// Apply discount if it's a custom rate card
|
||||
const proposedRate = r.clientRate * (1 - discount / 100);
|
||||
@@ -470,6 +470,9 @@ function VendorCompanyPricebookView({
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{RATE_CARDS.map((tab) => {
|
||||
const cardData = customRateCards.find((c) => c.name === tab);
|
||||
if (!cardData) {
|
||||
return null;
|
||||
}
|
||||
const isRenaming = renamingCard === tab;
|
||||
|
||||
if (isRenaming) {
|
||||
|
||||
@@ -6,10 +6,8 @@ import { InvoiceStatus } from "@/dataconnect-generated";
|
||||
import { useGetInvoiceById, useUpdateInvoice, useListRecentPaymentsByInvoiceId } from "@/dataconnect-generated/react";
|
||||
import { dataConnect } from "@/features/auth/firebase";
|
||||
import DashboardLayout from "@/features/layouts/DashboardLayout";
|
||||
import type { RootState } from "@/store/store";
|
||||
import { format, parseISO } from "date-fns";
|
||||
import { ArrowLeft, Download, Mail, CheckCircle, FileText, User, Calendar, MapPin, DollarSign } from "lucide-react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
const statusConfig: Record<string, { label: string; className: string }> = {
|
||||
@@ -25,14 +23,13 @@ const statusConfig: Record<string, { label: string; className: string }> = {
|
||||
export default function InvoiceDetail() {
|
||||
const navigate = useNavigate();
|
||||
const { id: invoiceId } = useParams<{ id: string }>();
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
|
||||
// Fetch Invoice Data
|
||||
const { data: invoiceData, isLoading: loadingInvoice } = useGetInvoiceById(dataConnect, { id: invoiceId! });
|
||||
const invoice = invoiceData?.invoice;
|
||||
|
||||
// Fetch Payment History
|
||||
const { data: paymentsData, isLoading: loadingPayments } = useListRecentPaymentsByInvoiceId(dataConnect, { invoiceId: invoiceId! });
|
||||
const { data: paymentsData } = useListRecentPaymentsByInvoiceId(dataConnect, { invoiceId: invoiceId! });
|
||||
const payments = paymentsData?.recentPayments || [];
|
||||
|
||||
// Mutations
|
||||
|
||||
@@ -15,12 +15,9 @@ import { useToast } from "@/common/components/ui/use-toast";
|
||||
import { InvoiceStatus, InovicePaymentTerms } from "@/dataconnect-generated";
|
||||
import {
|
||||
useCreateInvoice,
|
||||
useCreateInvoiceTemplate,
|
||||
useDeleteInvoiceTemplate,
|
||||
useGetInvoiceById,
|
||||
useListBusinesses,
|
||||
useListInvoices,
|
||||
useListInvoiceTemplates,
|
||||
useListOrders,
|
||||
useListStaff,
|
||||
useListVendorRates,
|
||||
@@ -71,9 +68,6 @@ export default function InvoiceEditor() {
|
||||
const { data: staffData } = useListStaff(dataConnect);
|
||||
const staffDirectory = staffData?.staffs || [];
|
||||
|
||||
const { data: templatesData, refetch: refetchTemplates } = useListInvoiceTemplates(dataConnect);
|
||||
const templates = templatesData?.invoiceTemplates || [];
|
||||
|
||||
const { data: currentInvoiceData } = useGetInvoiceById(dataConnect, { id: effectiveInvoiceId || "" }, { enabled: isEdit && !!effectiveInvoiceId });
|
||||
const existingInvoice = currentInvoiceData?.invoice;
|
||||
|
||||
@@ -176,7 +170,7 @@ export default function InvoiceEditor() {
|
||||
phone: existingInvoice.business?.phone || "",
|
||||
email: existingInvoice.business?.email || "",
|
||||
address: existingInvoice.business?.address || "",
|
||||
manager_name: existingInvoice.business?.contactName || "",
|
||||
manager_name: existingInvoice.managerName || "",
|
||||
hub_name: existingInvoice.hub || "",
|
||||
vendor_id: existingInvoice.vendorNumber || ""
|
||||
},
|
||||
@@ -198,21 +192,6 @@ export default function InvoiceEditor() {
|
||||
const selectedBusiness = businesses.find(b => b.id === selectedClientId);
|
||||
if (!selectedBusiness || !position) return 0;
|
||||
|
||||
const businessName = selectedBusiness.businessName || "";
|
||||
const extractCompanyName = (name: string) => {
|
||||
if (!name) return '';
|
||||
return name.split(/\s*[-–]\s*/)[0].trim();
|
||||
};
|
||||
const mainCompanyName = extractCompanyName(businessName);
|
||||
|
||||
// Logic similar to MVP
|
||||
const clientSpecificRate = vendorRates.find(rate =>
|
||||
rate.roleName?.toLowerCase() === position.toLowerCase() &&
|
||||
rate.client_name === businessName // This field might be different in Data Connect, checking listVendorRates output
|
||||
);
|
||||
|
||||
if (clientSpecificRate) return clientSpecificRate.clientRate || 0;
|
||||
|
||||
const defaultRate = vendorRates.find(rate =>
|
||||
rate.roleName?.toLowerCase() === position.toLowerCase()
|
||||
);
|
||||
@@ -314,157 +293,6 @@ export default function InvoiceEditor() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleDuplicateInvoice = (invoice: any) => {
|
||||
const newStaffEntries = (invoice.roles || []).map((entry: any) => ({
|
||||
...entry,
|
||||
date: format(new Date(), 'MM/dd/yyyy')
|
||||
}));
|
||||
|
||||
const newInvoiceNumber = generateInvoiceNumber(invoice.business?.businessName || '', invoices);
|
||||
|
||||
setFormData({
|
||||
invoice_number: newInvoiceNumber,
|
||||
event_id: "",
|
||||
event_name: invoice.order?.eventName || "",
|
||||
invoice_date: format(new Date(), 'yyyy-MM-dd'),
|
||||
due_date: format(addDays(new Date(), 45), 'yyyy-MM-dd'),
|
||||
payment_terms: invoice.paymentTerms || "NET_45",
|
||||
hub: invoice.hub || "",
|
||||
manager: invoice.managerName || "",
|
||||
vendor_id: invoice.vendorNumber || "",
|
||||
department: invoice.order?.deparment || "",
|
||||
po_reference: invoice.order?.poReference || "",
|
||||
from_company: {
|
||||
name: invoice.vendor?.companyName || formData.from_company.name,
|
||||
address: invoice.vendor?.address || formData.from_company.address,
|
||||
phone: invoice.vendor?.phone || formData.from_company.phone,
|
||||
email: invoice.vendor?.email || formData.from_company.email,
|
||||
},
|
||||
to_company: {
|
||||
name: invoice.business?.businessName || "",
|
||||
phone: invoice.business?.phone || "",
|
||||
email: invoice.business?.email || "",
|
||||
address: invoice.business?.address || "",
|
||||
manager_name: invoice.business?.contactName || "",
|
||||
hub_name: invoice.hub || "",
|
||||
vendor_id: invoice.vendorNumber || ""
|
||||
},
|
||||
staff_entries: newStaffEntries,
|
||||
charges: invoice.charges || [],
|
||||
other_charges: invoice.otherCharges || 0,
|
||||
notes: invoice.notes || "",
|
||||
});
|
||||
|
||||
if (invoice.businessId) {
|
||||
setSelectedClientId(invoice.businessId);
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "✅ Invoice Duplicated",
|
||||
description: `Copied from ${invoice.invoiceNumber} - update dates and details as needed`,
|
||||
});
|
||||
};
|
||||
|
||||
const { mutate: createTemplate } = useCreateInvoiceTemplate(dataConnect);
|
||||
const { mutate: deleteTemplate } = useDeleteInvoiceTemplate(dataConnect);
|
||||
|
||||
const handleUseTemplate = (template: any) => {
|
||||
const newStaffEntries = (template.roles || []).map((entry: any) => ({
|
||||
...entry,
|
||||
name: "",
|
||||
date: format(new Date(), 'MM/dd/yyyy'),
|
||||
worked_hours: 0,
|
||||
regular_hours: 0,
|
||||
ot_hours: 0,
|
||||
dt_hours: 0,
|
||||
regular_value: 0,
|
||||
ot_value: 0,
|
||||
dt_value: 0,
|
||||
total: 0
|
||||
}));
|
||||
|
||||
const newInvoiceNumber = generateInvoiceNumber(template.business?.businessName || '', invoices);
|
||||
|
||||
setFormData((prev: any) => ({
|
||||
...prev,
|
||||
invoice_number: newInvoiceNumber,
|
||||
invoice_date: format(new Date(), 'yyyy-MM-dd'),
|
||||
due_date: format(addDays(new Date(), 45), 'yyyy-MM-dd'),
|
||||
payment_terms: template.paymentTerms || "NET_45",
|
||||
hub: template.hub || "",
|
||||
department: "",
|
||||
po_reference: template.order?.poReference || "",
|
||||
from_company: {
|
||||
name: template.vendor?.companyName || prev.from_company.name,
|
||||
address: template.vendor?.address || prev.from_company.address,
|
||||
phone: template.vendor?.phone || prev.from_company.phone,
|
||||
email: template.vendor?.email || prev.from_company.email,
|
||||
},
|
||||
to_company: {
|
||||
name: template.business?.businessName || "",
|
||||
phone: template.business?.phone || "",
|
||||
email: template.business?.email || "",
|
||||
address: template.business?.address || "",
|
||||
manager_name: template.business?.contactName || "",
|
||||
hub_name: template.hub || "",
|
||||
vendor_id: template.vendorNumber || ""
|
||||
},
|
||||
staff_entries: newStaffEntries,
|
||||
charges: template.charges || [],
|
||||
notes: template.notes || "",
|
||||
}));
|
||||
|
||||
if (template.businessId) {
|
||||
setSelectedClientId(template.businessId);
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "✅ Template Applied",
|
||||
description: `Applied "${template.name}" - fill in staff names and times`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSaveTemplate = async (templateName: string) => {
|
||||
const selectedBusiness = businesses.find(b => b.id === selectedClientId);
|
||||
|
||||
await createTemplate({
|
||||
name: templateName,
|
||||
ownerId: "00000000-0000-0000-0000-000000000000", // placeholder, usually from auth
|
||||
businessId: selectedClientId || undefined,
|
||||
vendorId: "00000000-0000-0000-0000-000000000000", // placeholder
|
||||
paymentTerms: formData.payment_terms as InovicePaymentTerms,
|
||||
invoiceNumber: formData.invoice_number,
|
||||
issueDate: new Date(formData.invoice_date).toISOString(),
|
||||
dueDate: new Date(formData.due_date).toISOString(),
|
||||
hub: formData.hub,
|
||||
managerName: formData.manager,
|
||||
roles: formData.staff_entries,
|
||||
charges: formData.charges,
|
||||
otherCharges: parseFloat(formData.other_charges) || 0,
|
||||
subtotal: totals.subtotal,
|
||||
amount: totals.grandTotal,
|
||||
notes: formData.notes,
|
||||
staffCount: formData.staff_entries.length,
|
||||
chargesCount: formData.charges.length,
|
||||
});
|
||||
|
||||
refetchTemplates();
|
||||
|
||||
toast({
|
||||
title: "✅ Template Saved",
|
||||
description: `"${templateName}" can now be reused for future invoices`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteTemplate = async (templateId: string) => {
|
||||
await deleteTemplate({ id: templateId });
|
||||
refetchTemplates();
|
||||
toast({
|
||||
title: "Template Deleted",
|
||||
description: "Template has been removed",
|
||||
});
|
||||
};
|
||||
|
||||
const parseTimeToMinutes = (timeStr: string) => {
|
||||
if (!timeStr || timeStr === "hh:mm") return null;
|
||||
const match = timeStr.match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i);
|
||||
@@ -747,11 +575,11 @@ export default function InvoiceEditor() {
|
||||
<h3 className="font-bold text-red-800">Disputed Items Highlighted</h3>
|
||||
<p className="text-sm text-red-700 mt-1">
|
||||
{disputedIndices.length} line item(s) have been disputed by the client.
|
||||
<strong> Reason: </strong>{existingInvoice.dispute_reason || "Not specified"}
|
||||
<strong> Reason: </strong>{existingInvoice.disputeReason || "Not specified"}
|
||||
</p>
|
||||
{existingInvoice.dispute_details && (
|
||||
{existingInvoice.disputeDetails && (
|
||||
<p className="text-sm text-red-600 mt-2 bg-white p-2 rounded border border-red-200">
|
||||
"{existingInvoice.dispute_details}"
|
||||
"{existingInvoice.disputeDetails}"
|
||||
</p>
|
||||
)}
|
||||
<p className="text-xs text-red-600 mt-2">
|
||||
@@ -775,7 +603,7 @@ export default function InvoiceEditor() {
|
||||
<p className="text-xs text-muted-text">Quickly fill invoice from a completed event's shifts</p>
|
||||
</div>
|
||||
</div>
|
||||
<Select onValueChange={(val) => {
|
||||
<Select onValueChange={(val: string) => {
|
||||
const event = events.find(e => e.id === val);
|
||||
if (event) handleImportFromEvent(event);
|
||||
}}>
|
||||
|
||||
@@ -59,7 +59,6 @@ export default function InvoiceList() {
|
||||
// If user is client, they should see their invoices. If admin, they see all.
|
||||
const userRole = user?.userRole?.toUpperCase();
|
||||
const isClient = userRole === "CLIENT";
|
||||
const isVendor = userRole === "VENDOR";
|
||||
|
||||
if (isClient && inv.businessId !== user?.uid) return false;
|
||||
// In a real scenario, we'd match vendorId for vendor users
|
||||
|
||||
@@ -113,7 +113,7 @@ export default function EditOrder() {
|
||||
requested: totalRequested,
|
||||
total: eventData.total,
|
||||
poReference: eventData.po_reference,
|
||||
});
|
||||
} as any);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -146,7 +146,7 @@ export default function EditOrder() {
|
||||
staffName: s.staffName || s.staff_name,
|
||||
role: s.role
|
||||
}))
|
||||
});
|
||||
} as any);
|
||||
|
||||
setShowReductionAlert(false);
|
||||
setPendingUpdate(null);
|
||||
|
||||
@@ -105,7 +105,6 @@ export default function OrderDetail() {
|
||||
// Fetch real shift roles to get IDs and accurate counts
|
||||
const {
|
||||
data: shiftRolesData,
|
||||
isLoading: isLoadingShifts,
|
||||
refetch: refetchShifts
|
||||
} = useListShiftRolesByBusinessAndOrder(
|
||||
dataConnect,
|
||||
@@ -145,7 +144,7 @@ export default function OrderDetail() {
|
||||
cancelMutation.mutate({
|
||||
id,
|
||||
status: OrderStatus.CANCELLED,
|
||||
});
|
||||
} as any);
|
||||
};
|
||||
|
||||
const handleEdit = () => {
|
||||
|
||||
@@ -13,21 +13,19 @@ import DashboardLayout from "@/features/layouts/DashboardLayout";
|
||||
import {
|
||||
Search,
|
||||
Calendar,
|
||||
Filter,
|
||||
ArrowRight,
|
||||
Clock,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
XCircle,
|
||||
FileText
|
||||
} from "lucide-react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/store/store";
|
||||
import { useListOrders, useListBusinesses } from "@/dataconnect-generated/react";
|
||||
import { dataConnect } from "@/features/auth/firebase";
|
||||
import { format, isWithinInterval, parseISO, startOfDay, endOfDay } from "date-fns";
|
||||
import { format, isWithinInterval, startOfDay, endOfDay } from "date-fns";
|
||||
import { OrderStatus } from "@/dataconnect-generated";
|
||||
|
||||
export default function OrderList() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useMemo } from "react";
|
||||
import { Search, Check, X, UserPlus, Star, Clock, AlertTriangle } from "lucide-react";
|
||||
import { format, isSameDay, parseISO } from "date-fns";
|
||||
import { useState, useMemo } from "react";
|
||||
import { Search, Check, Star, Clock, AlertTriangle } from "lucide-react";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
@@ -20,7 +19,6 @@ import {
|
||||
useCreateAssignment,
|
||||
useUpdateShiftRole,
|
||||
useListAssignments,
|
||||
useListStaffAvailabilitiesByDay
|
||||
} from "@/dataconnect-generated/react";
|
||||
import { dataConnect } from "@/features/auth/firebase";
|
||||
import { AssignmentStatus } from "@/dataconnect-generated";
|
||||
@@ -38,7 +36,6 @@ export default function AssignStaffModal({ isOpen, onClose, shift, onSuccess }:
|
||||
const [selectedStaff, setSelectedStaff] = useState<any>(null);
|
||||
|
||||
const vendorId = shift.shift?.order?.vendorId || shift.order?.vendorId;
|
||||
const shiftDate = shift.shift?.date || shift.date;
|
||||
const shiftStartTime = shift.startTime || shift.start;
|
||||
const shiftEndTime = shift.endTime || shift.end;
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ export default function Schedule() {
|
||||
updateOrderMutation.mutate({
|
||||
id: rescheduleData.order.id,
|
||||
date: rescheduleData.newDate.toISOString()
|
||||
});
|
||||
} as any);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function TaskBoard() {
|
||||
const [conditionalColoring, setConditionalColoring] = useState(true);
|
||||
|
||||
// Queries
|
||||
const { data: shiftsData, isLoading: shiftsLoading } = useListShifts(dataConnect);
|
||||
const { data: shiftsData } = useListShifts(dataConnect);
|
||||
const { data: clientsData } = useListBusinesses(dataConnect);
|
||||
|
||||
const shifts = useMemo(() => shiftsData?.shifts || [], [shiftsData]);
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function TaskCard({
|
||||
provided,
|
||||
onClick,
|
||||
itemHeight = "normal",
|
||||
conditionalColoring = true
|
||||
conditionalColoring: _conditionalColoring = true
|
||||
}: TaskCardProps) {
|
||||
const heightClasses = {
|
||||
compact: "p-3",
|
||||
|
||||
@@ -164,7 +164,8 @@ export default function AddStaff() {
|
||||
<Checkbox
|
||||
id={skill}
|
||||
checked={field.value?.includes(skill)}
|
||||
onCheckedChange={(checked) => {
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
const updatedSkills = checked
|
||||
? [...(field.value || []), skill]
|
||||
: field.value?.filter((s: string) => s !== skill);
|
||||
|
||||
@@ -18,7 +18,7 @@ const ITEMS_PER_PAGE = 10;
|
||||
function StaffActiveStatus({ staffId }: { staffId: string }) {
|
||||
const { data: staffDetail, isLoading } = useGetStaffById(dataConnect, { id: staffId });
|
||||
|
||||
const getLastActiveText = (lastActive?: string) => {
|
||||
const getLastActiveText = (lastActive?: string | null) => {
|
||||
if (!lastActive) return 'Never';
|
||||
try {
|
||||
const date = new Date(lastActive);
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function EmployeeCard({ staff }: EmployeeCardProps) {
|
||||
const coveragePercentage = staff.shift_coverage_percentage || 0;
|
||||
const cancellationCount = staff.cancellation_count || 0;
|
||||
const noShowCount = staff.no_show_count || 0;
|
||||
const rating = staff.rating || 0;
|
||||
const rating = staff.averageRating || 0;
|
||||
const reliabilityScore = staff.reliability_score || 0;
|
||||
const reliability = getReliabilityColor(reliabilityScore);
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ export default function DocumentVault() {
|
||||
const filteredStaff = useMemo(() => {
|
||||
let result = [...staff];
|
||||
if (isVendor && user?.uid) {
|
||||
result = result.filter(s => s.ownerId === user.uid || s.createdBy === user.email);
|
||||
result = result.filter(s => s.ownerId === user.uid);
|
||||
}
|
||||
if (searchTerm) {
|
||||
result = result.filter(s =>
|
||||
@@ -472,7 +472,7 @@ export default function DocumentVault() {
|
||||
<thead>
|
||||
<tr className="border-b border-gray-100">
|
||||
<th className="text-left py-6 px-8 font-bold text-gray-400 text-[10px] uppercase tracking-wider min-w-[280px]">Employees</th>
|
||||
{availableDocTypes.map((type, idx) => (
|
||||
{availableDocTypes.map((type) => (
|
||||
<th key={type.documentType} className="p-4 min-w-[160px]">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<span className="font-bold text-gray-600 text-[10px] uppercase tracking-wider">{type.name}</span>
|
||||
|
||||
Reference in New Issue
Block a user