Add rank to reference
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -22,6 +22,7 @@ const referenceSchema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
description: z.string(),
|
||||
websiteUrl: z.string().url("Please enter a valid URL"),
|
||||
rank: z.number().int().positive().optional(),
|
||||
});
|
||||
|
||||
type ReferenceFormData = z.infer<typeof referenceSchema>;
|
||||
@@ -33,6 +34,7 @@ export type TrustCenterReferenceDialogRef = {
|
||||
name: string;
|
||||
description: string;
|
||||
websiteUrl: string;
|
||||
rank: number;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
@@ -41,6 +43,7 @@ type Reference = {
|
||||
name: string;
|
||||
description: string;
|
||||
websiteUrl: string;
|
||||
rank: number;
|
||||
};
|
||||
|
||||
export const TrustCenterReferenceDialog = forwardRef<TrustCenterReferenceDialogRef, { children?: ReactNode }>(
|
||||
@@ -89,6 +92,7 @@ export const TrustCenterReferenceDialog = forwardRef<TrustCenterReferenceDialogR
|
||||
name: reference.name,
|
||||
description: reference.description,
|
||||
websiteUrl: reference.websiteUrl,
|
||||
rank: reference.rank,
|
||||
});
|
||||
dialogRef.current?.open();
|
||||
},
|
||||
@@ -133,6 +137,7 @@ export const TrustCenterReferenceDialog = forwardRef<TrustCenterReferenceDialogR
|
||||
name: string;
|
||||
description: string;
|
||||
websiteUrl: string;
|
||||
rank?: number;
|
||||
logoFile?: null;
|
||||
} = {
|
||||
id: editReference.id,
|
||||
@@ -141,6 +146,10 @@ export const TrustCenterReferenceDialog = forwardRef<TrustCenterReferenceDialogR
|
||||
websiteUrl: data.websiteUrl,
|
||||
};
|
||||
|
||||
if (data.rank !== undefined) {
|
||||
input.rank = data.rank;
|
||||
}
|
||||
|
||||
const uploadables: Record<string, File> = {};
|
||||
|
||||
if (uploadedFile) {
|
||||
@@ -210,6 +219,18 @@ export const TrustCenterReferenceDialog = forwardRef<TrustCenterReferenceDialogR
|
||||
placeholder={__("https://example.com")}
|
||||
/>
|
||||
|
||||
{mode === 'edit' && (
|
||||
<Field
|
||||
{...register("rank", { valueAsNumber: true })}
|
||||
label={__("Rank")}
|
||||
type="number"
|
||||
min={1}
|
||||
error={errors.rank?.message}
|
||||
placeholder={__("Display order (1, 2, 3...)")}
|
||||
help={__("Lower numbers appear first")}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Field label={__("Logo")}>
|
||||
<Dropzone
|
||||
description={__("Upload logo image (PNG, JPG, WEBP up to 5MB)")}
|
||||
|
||||
@@ -14,9 +14,10 @@ import {
|
||||
IconPencil,
|
||||
IconArrowLink,
|
||||
} from "@probo/ui";
|
||||
import { type ReactNode, useRef } from "react";
|
||||
import { type ReactNode, useRef, useState } from "react";
|
||||
import {
|
||||
useTrustCenterReferences,
|
||||
useUpdateTrustCenterReferenceRankMutation,
|
||||
} from "/hooks/graph/TrustCenterReferenceGraph";
|
||||
import { TrustCenterReferenceDialog, type TrustCenterReferenceDialogRef } from "./TrustCenterReferenceDialog";
|
||||
import { DeleteTrustCenterReferenceDialog } from "./DeleteTrustCenterReferenceDialog";
|
||||
@@ -32,6 +33,7 @@ type Reference = {
|
||||
description: string;
|
||||
websiteUrl: string;
|
||||
logoUrl: string;
|
||||
rank: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
@@ -39,7 +41,11 @@ type Reference = {
|
||||
export function TrustCenterReferencesSection({ trustCenterId }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const dialogRef = useRef<TrustCenterReferenceDialogRef>(null);
|
||||
const data = useTrustCenterReferences(trustCenterId);
|
||||
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
|
||||
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
||||
const [refetchKey, setRefetchKey] = useState(0);
|
||||
const data = useTrustCenterReferences(trustCenterId, refetchKey);
|
||||
const [updateRank] = useUpdateTrustCenterReferenceRankMutation();
|
||||
|
||||
const trustCenterNode = data?.node;
|
||||
const references = trustCenterNode?.references?.edges?.map((edge) => edge.node) || [];
|
||||
@@ -55,11 +61,47 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
|
||||
dialogRef.current?.openEdit(reference);
|
||||
};
|
||||
|
||||
|
||||
const handleVisitWebsite = (websiteUrl: string) => {
|
||||
safeOpenUrl(websiteUrl);
|
||||
};
|
||||
|
||||
const handleDragStart = (index: number) => {
|
||||
setDraggedIndex(index);
|
||||
};
|
||||
|
||||
const handleDragOver = (e: React.DragEvent, index: number) => {
|
||||
e.preventDefault();
|
||||
if (draggedIndex !== index) {
|
||||
setDragOverIndex(index);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrop = (targetIndex: number) => {
|
||||
if (draggedIndex === null || draggedIndex === targetIndex) {
|
||||
setDraggedIndex(null);
|
||||
setDragOverIndex(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const draggedRef = references[draggedIndex];
|
||||
const targetRank = references[targetIndex].rank;
|
||||
|
||||
updateRank({
|
||||
variables: {
|
||||
input: {
|
||||
id: draggedRef.id,
|
||||
rank: targetRank,
|
||||
},
|
||||
},
|
||||
onCompleted: () => {
|
||||
setRefetchKey((prev) => prev + 1);
|
||||
},
|
||||
});
|
||||
|
||||
setDraggedIndex(null);
|
||||
setDragOverIndex(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -93,18 +135,28 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{references.map((reference: Reference) => (
|
||||
{references.map((reference: Reference, index: number) => (
|
||||
<ReferenceRow
|
||||
key={reference.id}
|
||||
reference={reference}
|
||||
index={index}
|
||||
isDragging={draggedIndex === index}
|
||||
isDropTarget={dragOverIndex === index && draggedIndex !== index}
|
||||
onEdit={() => handleEdit(reference)}
|
||||
connectionId={referencesConnectionId}
|
||||
onVisitWebsite={() => handleVisitWebsite(reference.websiteUrl)}
|
||||
onDragStart={() => handleDragStart(index)}
|
||||
onDragOver={(e) => handleDragOver(e, index)}
|
||||
onDrop={() => handleDrop(index)}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
|
||||
<p className="text-xs text-txt-tertiary">
|
||||
{__("Drag and drop references to change their displayed order")}
|
||||
</p>
|
||||
|
||||
<TrustCenterReferenceDialog ref={dialogRef} />
|
||||
</div>
|
||||
);
|
||||
@@ -112,14 +164,48 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
|
||||
|
||||
type ReferenceRowProps = {
|
||||
reference: Reference;
|
||||
index: number;
|
||||
isDragging: boolean;
|
||||
isDropTarget: boolean;
|
||||
onEdit: () => void;
|
||||
connectionId: string;
|
||||
onVisitWebsite: () => void;
|
||||
onDragStart: () => void;
|
||||
onDragOver: (e: React.DragEvent) => void;
|
||||
onDrop: () => void;
|
||||
};
|
||||
|
||||
function ReferenceRow({ reference, onEdit, connectionId, onVisitWebsite }: ReferenceRowProps) {
|
||||
function ReferenceRow({
|
||||
reference,
|
||||
isDragging,
|
||||
isDropTarget,
|
||||
onEdit,
|
||||
connectionId,
|
||||
onVisitWebsite,
|
||||
onDragStart,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
}: ReferenceRowProps) {
|
||||
const [isMouseDown, setIsMouseDown] = useState(false);
|
||||
|
||||
const className = [
|
||||
isDragging && "opacity-50 cursor-grabbing",
|
||||
!isDragging && !isMouseDown && "cursor-grab",
|
||||
!isDragging && isMouseDown && "cursor-grabbing",
|
||||
isDropTarget && "!bg-primary-50 border-y-2 border-primary-500",
|
||||
].filter(Boolean).join(" ");
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Tr
|
||||
draggable
|
||||
onDragStart={onDragStart}
|
||||
onDragOver={onDragOver}
|
||||
onDrop={onDrop}
|
||||
onMouseDown={() => setIsMouseDown(true)}
|
||||
onMouseUp={() => setIsMouseDown(false)}
|
||||
onMouseLeave={() => setIsMouseDown(false)}
|
||||
className={className}
|
||||
>
|
||||
<Td>
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar
|
||||
|
||||
Reference in New Issue
Block a user