mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-04-12 13:59:35 +02:00
Minor search UX improvements + Critical connector fixes (#2259)
This commit is contained in:
parent
8f26728a29
commit
183569061b
@ -170,7 +170,7 @@ def associate_credential_to_connector(
|
||||
connector_id=connector_id,
|
||||
credential_id=credential_id,
|
||||
cc_pair_name=metadata.name,
|
||||
is_public=metadata.is_public or True,
|
||||
is_public=True if metadata.is_public is None else metadata.is_public,
|
||||
groups=metadata.groups,
|
||||
)
|
||||
|
||||
|
@ -552,6 +552,7 @@ def create_connector_from_model(
|
||||
try:
|
||||
_validate_connector_allowed(connector_data.source)
|
||||
connector_base = _check_connector_permissions(connector_data, user)
|
||||
|
||||
return create_connector(
|
||||
db_session=db_session,
|
||||
connector_data=connector_base,
|
||||
|
@ -112,7 +112,7 @@ export default function Page() {
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onKeyDown={handleKeyPress}
|
||||
className="flex mt-2 max-w-sm h-9 w-full rounded-md border-2 border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
className="ml-1 w-96 h-9 flex-none rounded-md border border-border bg-background-50 px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
/>
|
||||
|
||||
{Object.entries(categorizedSources)
|
||||
|
@ -44,7 +44,6 @@ import {
|
||||
IsPublicGroupSelectorFormType,
|
||||
} from "@/components/IsPublicGroupSelector";
|
||||
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
|
||||
import { AdminBooleanFormField } from "@/components/credentials/CredentialFields";
|
||||
|
||||
export type AdvancedConfigFinal = {
|
||||
pruneFreq: number | null;
|
||||
@ -489,6 +488,7 @@ export default function AddConnector({
|
||||
}}
|
||||
>
|
||||
{(formikProps) => {
|
||||
console.log(formikProps.values);
|
||||
setFormValues(formikProps.values);
|
||||
handleFormStatusChange(
|
||||
formikProps.isValid && isFormSubmittable(formikProps.values)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { SubLabel } from "@/components/admin/connectors/Field";
|
||||
import { Field } from "formik";
|
||||
import { Field, useFormikContext } from "formik";
|
||||
|
||||
export default function NumberInput({
|
||||
label,
|
||||
@ -8,6 +8,7 @@ export default function NumberInput({
|
||||
description,
|
||||
name,
|
||||
showNeverIfZero,
|
||||
onChange,
|
||||
}: {
|
||||
value?: number;
|
||||
label: string;
|
||||
@ -15,7 +16,10 @@ export default function NumberInput({
|
||||
optional?: boolean;
|
||||
description?: string;
|
||||
showNeverIfZero?: boolean;
|
||||
onChange?: (value: number) => void;
|
||||
}) {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-base font-medium text-text-700 mb-1">
|
||||
@ -29,6 +33,14 @@ export default function NumberInput({
|
||||
name={name}
|
||||
min="-1"
|
||||
value={value === 0 && showNeverIfZero ? "Never" : value}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue =
|
||||
e.target.value === "Never" ? 0 : Number(e.target.value);
|
||||
setFieldValue(name, newValue);
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
}
|
||||
}}
|
||||
className={`mt-2 block w-full px-3 py-2
|
||||
bg-white border border-gray-300 rounded-md
|
||||
text-sm shadow-sm placeholder-gray-400
|
||||
|
@ -92,7 +92,7 @@ const RerankingDetailsForm = forwardRef<
|
||||
.nullable()
|
||||
.oneOf(Object.values(RerankerProvider))
|
||||
.optional(),
|
||||
api_key: Yup.string().nullable(),
|
||||
rerank_api_key: Yup.string().nullable(),
|
||||
num_rerank: Yup.number().min(1, "Must be at least 1"),
|
||||
})}
|
||||
onSubmit={async (_, { setSubmitting }) => {
|
||||
@ -132,6 +132,7 @@ const RerankingDetailsForm = forwardRef<
|
||||
...values,
|
||||
rerank_provider_type: card.rerank_provider_type!,
|
||||
rerank_model_name: card.modelName,
|
||||
rerank_api_key: null,
|
||||
});
|
||||
setFieldValue(
|
||||
"rerank_provider_type",
|
||||
@ -192,15 +193,18 @@ const RerankingDetailsForm = forwardRef<
|
||||
>
|
||||
<div className="w-full px-4">
|
||||
<TextFormField
|
||||
placeholder={values.api_key || undefined}
|
||||
placeholder={values.rerank_api_key || undefined}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setRerankingDetails({ ...values, api_key: value });
|
||||
setFieldValue("api_key", value);
|
||||
setRerankingDetails({
|
||||
...values,
|
||||
rerank_api_key: value,
|
||||
});
|
||||
setFieldValue("rerank_api_key", value);
|
||||
}}
|
||||
type="password"
|
||||
label="Cohere API Key"
|
||||
name="api_key"
|
||||
name="rerank_api_key"
|
||||
/>
|
||||
<div className="mt-4 flex justify-between">
|
||||
<Button
|
||||
|
@ -4,7 +4,7 @@ import { NonNullChain } from "typescript";
|
||||
export interface RerankingDetails {
|
||||
rerank_model_name: string | null;
|
||||
rerank_provider_type: RerankerProvider | null;
|
||||
api_key: string | null;
|
||||
rerank_api_key: string | null;
|
||||
num_rerank: number;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ interface AdvancedEmbeddingFormPageProps {
|
||||
) => void;
|
||||
advancedEmbeddingDetails: AdvancedSearchConfiguration;
|
||||
numRerank: number;
|
||||
updateNumRerank: (value: number) => void;
|
||||
}
|
||||
|
||||
const AdvancedEmbeddingFormPage = forwardRef<
|
||||
@ -22,7 +23,12 @@ const AdvancedEmbeddingFormPage = forwardRef<
|
||||
AdvancedEmbeddingFormPageProps
|
||||
>(
|
||||
(
|
||||
{ updateAdvancedEmbeddingDetails, advancedEmbeddingDetails, numRerank },
|
||||
{
|
||||
updateAdvancedEmbeddingDetails,
|
||||
advancedEmbeddingDetails,
|
||||
numRerank,
|
||||
updateNumRerank,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
@ -154,6 +160,10 @@ const AdvancedEmbeddingFormPage = forwardRef<
|
||||
name="disableRerankForStreaming"
|
||||
/>
|
||||
<NumberInput
|
||||
onChange={(value: number) => {
|
||||
updateNumRerank(value);
|
||||
setFieldValue("num_rerank", value);
|
||||
}}
|
||||
description="Number of results to rerank"
|
||||
optional={false}
|
||||
value={values.num_rerank}
|
||||
|
@ -44,7 +44,7 @@ export default function EmbeddingForm() {
|
||||
});
|
||||
|
||||
const [rerankingDetails, setRerankingDetails] = useState<RerankingDetails>({
|
||||
api_key: "",
|
||||
rerank_api_key: "",
|
||||
num_rerank: 0,
|
||||
rerank_provider_type: null,
|
||||
rerank_model_name: "",
|
||||
@ -118,7 +118,7 @@ export default function EmbeddingForm() {
|
||||
searchSettings.disable_rerank_for_streaming,
|
||||
});
|
||||
setRerankingDetails({
|
||||
api_key: searchSettings.api_key,
|
||||
rerank_api_key: searchSettings.rerank_api_key,
|
||||
num_rerank: searchSettings.num_rerank,
|
||||
rerank_provider_type: searchSettings.rerank_provider_type,
|
||||
rerank_model_name: searchSettings.rerank_model_name,
|
||||
@ -128,13 +128,13 @@ export default function EmbeddingForm() {
|
||||
|
||||
const originalRerankingDetails: RerankingDetails = searchSettings
|
||||
? {
|
||||
api_key: searchSettings.api_key,
|
||||
rerank_api_key: searchSettings.rerank_api_key,
|
||||
num_rerank: searchSettings.num_rerank,
|
||||
rerank_provider_type: searchSettings.rerank_provider_type,
|
||||
rerank_model_name: searchSettings.rerank_model_name,
|
||||
}
|
||||
: {
|
||||
api_key: "",
|
||||
rerank_api_key: "",
|
||||
num_rerank: 0,
|
||||
rerank_provider_type: null,
|
||||
rerank_model_name: "",
|
||||
@ -415,6 +415,12 @@ export default function EmbeddingForm() {
|
||||
<>
|
||||
<Card>
|
||||
<AdvancedEmbeddingFormPage
|
||||
updateNumRerank={(value: number) =>
|
||||
setRerankingDetails({
|
||||
...rerankingDetails,
|
||||
num_rerank: value,
|
||||
})
|
||||
}
|
||||
numRerank={rerankingDetails.num_rerank}
|
||||
advancedEmbeddingDetails={advancedEmbeddingDetails}
|
||||
updateAdvancedEmbeddingDetails={updateAdvancedEmbeddingDetails}
|
||||
|
@ -457,7 +457,7 @@ export function CCPairIndexingStatusTable({
|
||||
placeholder="Search connectors..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="ml-2 w-96 h-9 flex-none rounded-md border border-border bg-background-50 px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
className="ml-1 w-96 h-9 flex-none rounded-md border border-border bg-background-50 px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
/>
|
||||
|
||||
<Button className="h-9" onClick={() => toggleSources()}>
|
||||
|
@ -1,12 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
||||
import { useContext, useState, useRef, useLayoutEffect } from "react";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { Popover } from "@/components/popover/Popover";
|
||||
import { ChevronDownIcon } from "@/components/icons/icons";
|
||||
import { Divider } from "@tremor/react";
|
||||
import { MinimalMarkdown } from "@/components/chat_search/MinimalMarkdown";
|
||||
|
||||
export function ChatBanner() {
|
||||
@ -34,27 +31,6 @@ export function ChatBanner() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderMarkdown = (className: string) => (
|
||||
<ReactMarkdown
|
||||
className={`w-full text-wrap break-word ${className}`}
|
||||
components={{
|
||||
a: ({ node, ...props }) => (
|
||||
<a
|
||||
{...props}
|
||||
className="text-sm text-link hover:text-link-hover"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
/>
|
||||
),
|
||||
p: ({ node, ...props }) => (
|
||||
<p {...props} className="text-wrap break-word text-sm m-0 w-full" />
|
||||
),
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{settings.enterpriseSettings?.custom_header_content}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
@ -65,7 +41,6 @@ export function ChatBanner() {
|
||||
w-full
|
||||
mx-auto
|
||||
relative
|
||||
bg-background-100
|
||||
shadow-sm
|
||||
rounded
|
||||
border-l-8 border-l-400
|
||||
@ -81,7 +56,6 @@ export function ChatBanner() {
|
||||
className="line-clamp-2 text-center w-full overflow-hidden pr-8"
|
||||
>
|
||||
<MinimalMarkdown
|
||||
className=""
|
||||
content={settings.enterpriseSettings.custom_header_content}
|
||||
/>
|
||||
</div>
|
||||
@ -90,7 +64,6 @@ export function ChatBanner() {
|
||||
className="absolute top-0 left-0 invisible w-full"
|
||||
>
|
||||
<MinimalMarkdown
|
||||
className=""
|
||||
content={settings.enterpriseSettings.custom_header_content}
|
||||
/>
|
||||
</div>
|
||||
|
@ -22,7 +22,7 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
|
||||
enforceGroupSelection?: boolean;
|
||||
}) => {
|
||||
const { data: userGroups, isLoading: userGroupsIsLoading } = useUserGroups();
|
||||
const { isAdmin, user, isLoadingUser } = useUser();
|
||||
const { isAdmin, user, isLoadingUser, isCurator } = useUser();
|
||||
const [shouldHideContent, setShouldHideContent] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -87,42 +87,45 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
|
||||
)}
|
||||
|
||||
{(!formikProps.values.is_public ||
|
||||
!isAdmin ||
|
||||
formikProps.values.groups.length > 0) && (
|
||||
<>
|
||||
<div className="flex mt-4 gap-x-2 items-center">
|
||||
<div className="block font-medium text-base">
|
||||
Assign group access for this {objectName}
|
||||
</div>
|
||||
</div>
|
||||
<Text className="mb-3">
|
||||
{isAdmin || !enforceGroupSelection ? (
|
||||
<>
|
||||
This {objectName} will be visible/accessible by the groups
|
||||
selected below
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Curators must select one or more groups to give access to this{" "}
|
||||
{objectName}
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<FieldArray
|
||||
name="groups"
|
||||
render={(arrayHelpers: ArrayHelpers) => (
|
||||
<div className="flex gap-2 flex-wrap mb-4">
|
||||
{userGroupsIsLoading ? (
|
||||
<div className="animate-pulse bg-gray-200 h-8 w-32 rounded"></div>
|
||||
isCurator ||
|
||||
formikProps.values.groups.length > 0) &&
|
||||
(userGroupsIsLoading ? (
|
||||
<div className="animate-pulse bg-gray-200 h-8 w-32 rounded"></div>
|
||||
) : (
|
||||
userGroups &&
|
||||
userGroups.length > 0 && (
|
||||
<>
|
||||
<div className="flex mt-4 gap-x-2 items-center">
|
||||
<div className="block font-medium text-base">
|
||||
Assign group access for this {objectName}
|
||||
</div>
|
||||
</div>
|
||||
<Text className="mb-3">
|
||||
{isAdmin || !enforceGroupSelection ? (
|
||||
<>
|
||||
This {objectName} will be visible/accessible by the groups
|
||||
selected below
|
||||
</>
|
||||
) : (
|
||||
userGroups &&
|
||||
userGroups.map((userGroup: UserGroup) => {
|
||||
const ind = formikProps.values.groups.indexOf(userGroup.id);
|
||||
let isSelected = ind !== -1;
|
||||
return (
|
||||
<div
|
||||
key={userGroup.id}
|
||||
className={`
|
||||
<>
|
||||
Curators must select one or more groups to give access to
|
||||
this {objectName}
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<FieldArray
|
||||
name="groups"
|
||||
render={(arrayHelpers: ArrayHelpers) => (
|
||||
<div className="flex gap-2 flex-wrap mb-4">
|
||||
{userGroups.map((userGroup: UserGroup) => {
|
||||
const ind = formikProps.values.groups.indexOf(
|
||||
userGroup.id
|
||||
);
|
||||
let isSelected = ind !== -1;
|
||||
return (
|
||||
<div
|
||||
key={userGroup.id}
|
||||
className={`
|
||||
px-3
|
||||
py-1
|
||||
rounded-lg
|
||||
@ -132,32 +135,33 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
|
||||
flex
|
||||
cursor-pointer
|
||||
${isSelected ? "bg-background-strong" : "hover:bg-hover"}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
arrayHelpers.remove(ind);
|
||||
} else {
|
||||
arrayHelpers.push(userGroup.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="my-auto flex">
|
||||
<FiUsers className="my-auto mr-2" /> {userGroup.name}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
arrayHelpers.remove(ind);
|
||||
} else {
|
||||
arrayHelpers.push(userGroup.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="my-auto flex">
|
||||
<FiUsers className="my-auto mr-2" />{" "}
|
||||
{userGroup.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<ErrorMessage
|
||||
name="groups"
|
||||
component="div"
|
||||
className="text-error text-sm mt-1"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<ErrorMessage
|
||||
name="groups"
|
||||
component="div"
|
||||
className="text-error text-sm mt-1"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -149,6 +149,17 @@ export function TextFormField({
|
||||
if (isTextArea && !heightString) {
|
||||
heightString = "h-28";
|
||||
}
|
||||
const [field, , helpers] = useField(name);
|
||||
const { setValue } = helpers;
|
||||
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => {
|
||||
setValue(e.target.value);
|
||||
if (onChange) {
|
||||
onChange(e as React.ChangeEvent<HTMLInputElement>);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
@ -200,7 +211,7 @@ export function TextFormField({
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
autoComplete={autoCompleteDisabled ? "off" : undefined}
|
||||
// onChange={onChange}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{includeRevert && (
|
||||
<div className="flex-none mt-auto">
|
||||
@ -396,17 +407,27 @@ export const BooleanFormField = ({
|
||||
alignTop,
|
||||
checked,
|
||||
}: BooleanFormFieldProps) => {
|
||||
const [field, meta, helpers] = useField<boolean>(name);
|
||||
const { setValue } = helpers;
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.target.checked);
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="flex text-sm">
|
||||
<Field
|
||||
disabled={disabled}
|
||||
name={name}
|
||||
checked={checked}
|
||||
type="checkbox"
|
||||
{...field}
|
||||
checked={checked !== undefined ? checked : field.value}
|
||||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
className={`${removeIndent ? "mr-2" : "mx-3"}
|
||||
px-5 w-3.5 h-3.5 ${alignTop ? "mt-1" : "my-auto"}`}
|
||||
{...(onChange ? { onChange } : {})}
|
||||
px-5 w-3.5 h-3.5 ${alignTop ? "mt-1" : "my-auto"}`}
|
||||
/>
|
||||
{!noLabel && (
|
||||
<div>
|
||||
|
@ -7,7 +7,6 @@ import { NewChatIcon } from "../icons/icons";
|
||||
import { NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA } from "@/lib/constants";
|
||||
import { ChatSession } from "@/app/chat/interfaces";
|
||||
import Link from "next/link";
|
||||
import { SettingsContext } from "../settings/SettingsProvider";
|
||||
import { pageType } from "@/app/chat/sessionSidebar/types";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ChatBanner } from "@/app/chat/ChatBanner";
|
||||
@ -65,7 +64,7 @@ export default function FunctionalHeader({
|
||||
router.push(newChatUrl);
|
||||
};
|
||||
return (
|
||||
<div className="pb-6 left-0 sticky top-0 z-20 w-full relative flex">
|
||||
<div className="left-0 sticky top-0 z-20 w-full relative flex">
|
||||
<div className="mt-2 mx-2.5 cursor-pointer text-text-700 relative flex w-full">
|
||||
<LogoType
|
||||
assistantId={currentChatSession?.persona_id}
|
||||
@ -130,7 +129,7 @@ export default function FunctionalHeader({
|
||||
</div>
|
||||
|
||||
{page != "assistants" && (
|
||||
<div className="h-24 left-0 absolute top-0 z-10 w-full bg-gradient-to-b via-50% z-[-1] from-background via-background to-background/10 flex" />
|
||||
<div className="h-20 left-0 absolute top-0 z-10 w-full bg-gradient-to-b via-50% z-[-1] from-background via-background to-background/10 flex" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -111,6 +111,19 @@ export function SourceSelector({
|
||||
<DateRangeSelector value={timeRange} onValueChange={setTimeRange} />
|
||||
</div>
|
||||
|
||||
{availableTags.length > 0 && (
|
||||
<>
|
||||
<div className="mt-4 mb-2">
|
||||
<SectionTitle>Tags</SectionTitle>
|
||||
</div>
|
||||
<TagFilter
|
||||
tags={availableTags}
|
||||
selectedTags={selectedTags}
|
||||
setSelectedTags={setSelectedTags}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{existingSources.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<div className="flex w-full gap-x-2 items-center">
|
||||
@ -191,19 +204,6 @@ export function SourceSelector({
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{availableTags.length > 0 && (
|
||||
<>
|
||||
<div className="mt-4 mb-2">
|
||||
<SectionTitle>Tags</SectionTitle>
|
||||
</div>
|
||||
<TagFilter
|
||||
tags={availableTags}
|
||||
selectedTags={selectedTags}
|
||||
setSelectedTags={setSelectedTags}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ export function TagFilter({
|
||||
<FiTag className="mr-1 my-auto" />
|
||||
Tags
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-x-1 gap-y-1">
|
||||
<div className="flex overflow-y-scroll max-h-96 flex-wrap gap-x-1 gap-y-1">
|
||||
{filteredTags.length > 0 ? (
|
||||
filteredTags.map((tag) => (
|
||||
<div
|
||||
|
@ -8,6 +8,7 @@ interface UserContextType {
|
||||
user: User | null;
|
||||
isLoadingUser: boolean;
|
||||
isAdmin: boolean;
|
||||
isCurator: boolean;
|
||||
refreshUser: () => Promise<void>;
|
||||
}
|
||||
|
||||
@ -17,12 +18,16 @@ export function UserProvider({ children }: { children: React.ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [isLoadingUser, setIsLoadingUser] = useState(true);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [isCurator, setIsCurator] = useState(false);
|
||||
|
||||
const fetchUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
setUser(user);
|
||||
setIsAdmin(user?.role === UserRole.ADMIN);
|
||||
setIsCurator(
|
||||
user?.role === UserRole.CURATOR || user?.role == UserRole.GLOBAL_CURATOR
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
} finally {
|
||||
@ -40,7 +45,9 @@ export function UserProvider({ children }: { children: React.ReactNode }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={{ user, isLoadingUser, isAdmin, refreshUser }}>
|
||||
<UserContext.Provider
|
||||
value={{ user, isLoadingUser, isAdmin, refreshUser, isCurator }}
|
||||
>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user