LibreChat/client/src/components/Prompts/SharePrompt.tsx
Marco Beretta fa9e778399
🔗 feat: Enhance Share Functionality, Optimize DataTable & Fix Critical Bugs (#5220)
* 🔄 refactor: frontend and backend share link logic; feat: qrcode for share link; feat: refresh link

* 🐛 fix: Conditionally render shared link and refactor share link creation logic

* 🐛 fix: Correct conditional check for shareId in ShareButton component

* 🔄 refactor: Update shared links API and data handling; improve query parameters and response structure

* 🔄 refactor: Update shared links pagination and response structure; replace pageNumber with cursor for improved data fetching

* 🔄 refactor: DataTable performance optimization

* fix: delete shared link cache update

* 🔄 refactor: Enhance shared links functionality; add conversationId to shared link model and update related components

* 🔄 refactor: Add delete functionality to SharedLinkButton; integrate delete mutation and confirmation dialog

* 🔄 feat: Add AnimatedSearchInput component with gradient animations and search functionality; update search handling in API and localization

* 🔄 refactor: Improve SharedLinks component; enhance delete functionality and loading states, optimize AnimatedSearchInput, and refine DataTable scrolling behavior

* fix: mutation type issues with deleted shared link mutation

* fix: MutationOptions types

* fix: Ensure only public shared links are retrieved in getSharedLink function

* fix: `qrcode.react` install location

* fix: ensure non-public shared links are not fetched when checking for existing shared links, and remove deprecated .exec() method for queries

* fix: types and import order

* refactor: cleanup share button UI logic, make more intuitive

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2025-01-21 09:31:05 -05:00

168 lines
5.4 KiB
TypeScript

import React, { useEffect, useMemo } from 'react';
import { Share2Icon } from 'lucide-react';
import { useForm, Controller } from 'react-hook-form';
import { Permissions } from 'librechat-data-provider';
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
import type {
TPromptGroup,
TStartupConfig,
TUpdatePromptGroupPayload,
} from 'librechat-data-provider';
import {
OGDialog,
OGDialogTitle,
OGDialogContent,
OGDialogTrigger,
OGDialogClose,
} from '~/components/ui';
import { useUpdatePromptGroup } from '~/data-provider';
import { Button, Switch } from '~/components/ui';
import { useToastContext } from '~/Providers';
import { useLocalize } from '~/hooks';
type FormValues = {
[Permissions.SHARED_GLOBAL]: boolean;
};
const SharePrompt = ({ group, disabled }: { group?: TPromptGroup; disabled: boolean }) => {
const localize = useLocalize();
const { showToast } = useToastContext();
const updateGroup = useUpdatePromptGroup();
const { data: startupConfig = {} as TStartupConfig, isFetching } = useGetStartupConfig();
const { instanceProjectId } = startupConfig;
const groupIsGlobal = useMemo(
() => !!(group?.projectIds ?? []).includes(instanceProjectId),
[group, instanceProjectId],
);
const {
control,
setValue,
getValues,
handleSubmit,
formState: { isSubmitting },
} = useForm<FormValues>({
mode: 'onChange',
defaultValues: {
[Permissions.SHARED_GLOBAL]: groupIsGlobal,
},
});
useEffect(() => {
setValue(Permissions.SHARED_GLOBAL, groupIsGlobal);
}, [groupIsGlobal, setValue]);
if (!group || !instanceProjectId) {
return null;
}
const onSubmit = (data: FormValues) => {
const groupId = group._id ?? '';
if (!groupId || !instanceProjectId) {
return;
}
const payload = {} as TUpdatePromptGroupPayload;
if (data[Permissions.SHARED_GLOBAL]) {
payload.projectIds = [startupConfig.instanceProjectId];
} else {
payload.removeProjectIds = [startupConfig.instanceProjectId];
}
updateGroup.mutate({
id: groupId,
payload,
});
};
return (
<OGDialog>
<OGDialogTrigger asChild>
<Button
variant="default"
size="sm"
className="h-10 w-10 border border-transparent bg-blue-500/90 p-0.5 transition-all hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-800"
disabled={disabled}
>
<Share2Icon className="size-5 cursor-pointer text-white" />
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-11/12 max-w-[600px]">
<OGDialogTitle className="truncate pr-2" title={group.name}>
{localize('com_ui_share_var', `"${group.name}"`)}
</OGDialogTitle>
<form className="p-2" onSubmit={handleSubmit(onSubmit)}>
<div className="mb-4 flex items-center justify-between gap-2 py-4">
<div className="flex items-center">
<button
type="button"
className="mr-2 cursor-pointer"
onClick={() =>
setValue(Permissions.SHARED_GLOBAL, !getValues(Permissions.SHARED_GLOBAL), {
shouldDirty: true,
})
}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setValue(Permissions.SHARED_GLOBAL, !getValues(Permissions.SHARED_GLOBAL), {
shouldDirty: true,
});
}
}}
aria-checked={getValues(Permissions.SHARED_GLOBAL)}
role="checkbox"
>
{localize('com_ui_share_to_all_users')}
</button>
<label htmlFor={Permissions.SHARED_GLOBAL} className="select-none">
{groupIsGlobal && (
<span className="ml-2 text-xs">{localize('com_ui_prompt_shared_to_all')}</span>
)}
</label>
</div>
<Controller
name={Permissions.SHARED_GLOBAL}
control={control}
disabled={isFetching || updateGroup.isLoading || !instanceProjectId}
rules={{
validate: (value) => {
const isValid = !(value && groupIsGlobal);
if (!isValid) {
showToast({
message: localize('com_ui_prompt_already_shared_to_all'),
status: 'warning',
});
}
return isValid;
},
}}
render={({ field }) => (
<Switch
{...field}
checked={field.value}
onCheckedChange={field.onChange}
value={field.value.toString()}
/>
)}
/>
</div>
<div className="flex justify-end">
<OGDialogClose asChild>
<button
type="submit"
disabled={isSubmitting || isFetching}
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
>
{localize('com_ui_save')}
</button>
</OGDialogClose>
</div>
</form>
</OGDialogContent>
</OGDialog>
);
};
export default SharePrompt;