mirror of
https://github.com/aykhans/slash-e.git
synced 2026-08-04 22:52:41 +00:00
chore: refactor sheet components
This commit is contained in:
@@ -7,7 +7,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
import { Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
import { useCollectionStore, useShortcutStore, useWorkspaceStore } from "@/stores";
|
import { useCollectionStore, useShortcutStore, useWorkspaceStore } from "@/stores";
|
||||||
import { Collection } from "@/types/proto/api/v1/collection_service";
|
import { Collection } from "@/types/proto/api/v1/collection_service";
|
||||||
@@ -163,107 +163,108 @@ const CreateCollectionDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={true} onOpenChange={onClose}>
|
<Sheet open={true} onOpenChange={onClose}>
|
||||||
<SheetContent className="w-full sm:max-w-md overflow-y-auto">
|
<SheetContent className="w-full sm:max-w-md">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>{isCreating ? "Create Collection" : "Edit Collection"}</SheetTitle>
|
<SheetTitle>{isCreating ? "Create Collection" : "Edit Collection"}</SheetTitle>
|
||||||
<SheetDescription>{isCreating ? "Create a new collection of shortcuts" : "Edit your collection details"}</SheetDescription>
|
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="mt-6 space-y-4">
|
<SheetBody>
|
||||||
<div className="space-y-2">
|
<div className="space-y-4">
|
||||||
<Label htmlFor="name">
|
<div className="space-y-2">
|
||||||
Name <span className="text-destructive">*</span>
|
<Label htmlFor="name">
|
||||||
</Label>
|
Name <span className="text-destructive">*</span>
|
||||||
<div className="flex items-center gap-2">
|
</Label>
|
||||||
<span className="text-sm text-muted-foreground">c/</span>
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm text-muted-foreground">c/</span>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="An easy name to remember"
|
||||||
|
value={state.collectionCreate.name}
|
||||||
|
onChange={handleNameInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="title">
|
||||||
|
Title <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="title"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="An easy name to remember"
|
placeholder="A short title of your collection"
|
||||||
value={state.collectionCreate.name}
|
value={state.collectionCreate.title}
|
||||||
onChange={handleNameInputChange}
|
onChange={handleTitleInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<Label htmlFor="description">Description</Label>
|
||||||
<Label htmlFor="title">
|
<Input
|
||||||
Title <span className="text-destructive">*</span>
|
id="description"
|
||||||
</Label>
|
type="text"
|
||||||
<Input
|
placeholder="A slightly longer description"
|
||||||
id="title"
|
value={state.collectionCreate.description}
|
||||||
type="text"
|
onChange={handleDescriptionInputChange}
|
||||||
placeholder="A short title of your collection"
|
/>
|
||||||
value={state.collectionCreate.title}
|
|
||||||
onChange={handleTitleInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="description">Description</Label>
|
|
||||||
<Input
|
|
||||||
id="description"
|
|
||||||
type="text"
|
|
||||||
placeholder="A slightly longer description"
|
|
||||||
value={state.collectionCreate.description}
|
|
||||||
onChange={handleDescriptionInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="public"
|
|
||||||
checked={state.collectionCreate.visibility === Visibility.PUBLIC}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
setPartialState({
|
|
||||||
collectionCreate: Object.assign(state.collectionCreate, {
|
|
||||||
visibility: checked ? Visibility.PUBLIC : Visibility.WORKSPACE,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="public" className="text-sm font-normal cursor-pointer">
|
|
||||||
{t(`shortcut.visibility.public.description`)}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-baseline gap-2">
|
|
||||||
<Label>Shortcuts</Label>
|
|
||||||
<span className="text-sm text-muted-foreground">({selectedShortcuts.length})</span>
|
|
||||||
{selectedShortcuts.length === 0 && <span className="text-sm italic text-muted-foreground">(Select a shortcut first)</span>}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full py-1 px-px flex flex-row justify-start items-start flex-wrap gap-2">
|
<div className="flex items-center space-x-2">
|
||||||
{selectedShortcuts.map((shortcut) => {
|
<Checkbox
|
||||||
return (
|
id="public"
|
||||||
<ShortcutView
|
checked={state.collectionCreate.visibility === Visibility.PUBLIC}
|
||||||
key={shortcut.id}
|
onCheckedChange={(checked) =>
|
||||||
className="!w-auto select-none max-w-[40%] cursor-pointer bg-muted shadow"
|
setPartialState({
|
||||||
shortcut={shortcut}
|
collectionCreate: Object.assign(state.collectionCreate, {
|
||||||
onClick={() => {
|
visibility: checked ? Visibility.PUBLIC : Visibility.WORKSPACE,
|
||||||
setSelectedShortcuts([...selectedShortcuts.filter((selectedShortcut) => selectedShortcut.id !== shortcut.id)]);
|
}),
|
||||||
}}
|
})
|
||||||
/>
|
}
|
||||||
);
|
/>
|
||||||
})}
|
<Label htmlFor="public" className="text-sm font-normal cursor-pointer">
|
||||||
{unselectedShortcuts.map((shortcut) => {
|
{t(`shortcut.visibility.public.description`)}
|
||||||
return (
|
</Label>
|
||||||
<ShortcutView
|
</div>
|
||||||
key={shortcut.id}
|
<Separator />
|
||||||
className="!w-auto select-none max-w-[40%] border-dashed cursor-pointer"
|
<div className="space-y-2">
|
||||||
shortcut={shortcut}
|
<div className="flex items-baseline gap-2">
|
||||||
onClick={() => {
|
<Label>Shortcuts</Label>
|
||||||
setSelectedShortcuts([...selectedShortcuts, shortcut]);
|
<span className="text-sm text-muted-foreground">({selectedShortcuts.length})</span>
|
||||||
}}
|
{selectedShortcuts.length === 0 && <span className="text-sm italic text-muted-foreground">(Select a shortcut first)</span>}
|
||||||
/>
|
</div>
|
||||||
);
|
<div className="w-full py-1 px-px flex flex-row justify-start items-start flex-wrap gap-2">
|
||||||
})}
|
{selectedShortcuts.map((shortcut) => {
|
||||||
{selectedShortcuts.length + unselectedShortcuts.length === 0 && (
|
return (
|
||||||
<div className="w-full flex flex-row justify-center items-center text-muted-foreground">
|
<ShortcutView
|
||||||
<Icon.PackageOpen className="w-6 h-auto" />
|
key={shortcut.id}
|
||||||
<p className="ml-2">No shortcuts found.</p>
|
className="!w-auto select-none max-w-[40%] cursor-pointer bg-muted shadow"
|
||||||
</div>
|
shortcut={shortcut}
|
||||||
)}
|
onClick={() => {
|
||||||
|
setSelectedShortcuts([...selectedShortcuts.filter((selectedShortcut) => selectedShortcut.id !== shortcut.id)]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{unselectedShortcuts.map((shortcut) => {
|
||||||
|
return (
|
||||||
|
<ShortcutView
|
||||||
|
key={shortcut.id}
|
||||||
|
className="!w-auto select-none max-w-[40%] border-dashed cursor-pointer"
|
||||||
|
shortcut={shortcut}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedShortcuts([...selectedShortcuts, shortcut]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{selectedShortcuts.length + unselectedShortcuts.length === 0 && (
|
||||||
|
<div className="w-full flex flex-row justify-center items-center text-muted-foreground">
|
||||||
|
<Icon.PackageOpen className="w-6 h-auto" />
|
||||||
|
<p className="ml-2">No shortcuts found.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SheetBody>
|
||||||
<SheetFooter className="mt-6">
|
<SheetFooter>
|
||||||
<Button variant="outline" disabled={requestState.isLoading} onClick={onClose}>
|
<Button variant="outline" disabled={requestState.isLoading} onClick={onClose}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
import { Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
||||||
import { workspaceServiceClient } from "@/grpcweb";
|
import { workspaceServiceClient } from "@/grpcweb";
|
||||||
import { absolutifyLink } from "@/helpers/utils";
|
import { absolutifyLink } from "@/helpers/utils";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
@@ -137,150 +137,136 @@ const CreateIdentityProviderDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={true} onOpenChange={onClose}>
|
<Sheet open={true} onOpenChange={onClose}>
|
||||||
<SheetContent className="w-full sm:max-w-md overflow-y-auto">
|
<SheetContent className="w-full sm:max-w-md">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>{isCreating ? "Create Identity Provider" : "Edit Identity Provider"}</SheetTitle>
|
<SheetTitle>{isCreating ? "Create Identity Provider" : "Edit Identity Provider"}</SheetTitle>
|
||||||
<SheetDescription>
|
|
||||||
{isCreating ? "Configure a new OAuth2 identity provider" : "Edit your identity provider settings"}
|
|
||||||
</SheetDescription>
|
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="mt-6 space-y-4">
|
<SheetBody>
|
||||||
<div className="space-y-2">
|
<div className="space-y-4">
|
||||||
<Label htmlFor="title">
|
<div className="space-y-2">
|
||||||
Title <span className="text-destructive">*</span>
|
<Label htmlFor="title">
|
||||||
</Label>
|
Title <span className="text-destructive">*</span>
|
||||||
<Input
|
</Label>
|
||||||
id="title"
|
<Input
|
||||||
type="text"
|
id="title"
|
||||||
placeholder="A short title will be displayed in the UI"
|
type="text"
|
||||||
value={state.identityProviderCreate.title}
|
placeholder="A short title will be displayed in the UI"
|
||||||
onChange={handleTitleInputChange}
|
value={state.identityProviderCreate.title}
|
||||||
/>
|
onChange={handleTitleInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm font-medium">Identity provider information</p>
|
||||||
|
{isCreating && (
|
||||||
|
<div className="rounded-md py-2 px-3 bg-secondary text-sm w-full break-all">
|
||||||
|
<span className="text-muted-foreground">Redirect URL</span>
|
||||||
|
<br />
|
||||||
|
<code className="text-xs">{absolutifyLink("/auth/callback")}</code>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="client-id">
|
||||||
|
Client ID <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="client-id"
|
||||||
|
type="text"
|
||||||
|
placeholder="Client ID of the OAuth2 provider"
|
||||||
|
value={state.identityProviderCreate.config?.oauth2?.clientId}
|
||||||
|
onChange={(e) => handleOAuth2ConfigChange(e, "clientId")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="client-secret">
|
||||||
|
Client Secret <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="client-secret"
|
||||||
|
type="text"
|
||||||
|
placeholder="Client Secret of the OAuth2 provider"
|
||||||
|
value={state.identityProviderCreate.config?.oauth2?.clientSecret}
|
||||||
|
onChange={(e) => handleOAuth2ConfigChange(e, "clientSecret")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="auth-url">
|
||||||
|
Authorization endpoint <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="auth-url"
|
||||||
|
type="text"
|
||||||
|
placeholder="Authorization endpoint of the OAuth2 provider"
|
||||||
|
value={state.identityProviderCreate.config?.oauth2?.authUrl}
|
||||||
|
onChange={(e) => handleOAuth2ConfigChange(e, "authUrl")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="token-url">
|
||||||
|
Token endpoint <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="token-url"
|
||||||
|
type="text"
|
||||||
|
placeholder="Token endpoint of the OAuth2 provider"
|
||||||
|
value={state.identityProviderCreate.config?.oauth2?.tokenUrl}
|
||||||
|
onChange={(e) => handleOAuth2ConfigChange(e, "tokenUrl")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="user-info-url">
|
||||||
|
User endpoint <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="user-info-url"
|
||||||
|
type="text"
|
||||||
|
placeholder="User endpoint of the OAuth2 provider"
|
||||||
|
value={state.identityProviderCreate.config?.oauth2?.userInfoUrl}
|
||||||
|
onChange={(e) => handleOAuth2ConfigChange(e, "userInfoUrl")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="scopes">
|
||||||
|
Scopes <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="scopes"
|
||||||
|
type="text"
|
||||||
|
placeholder="Scopes of the OAuth2 provider, separated by space"
|
||||||
|
value={state.identityProviderCreate.config?.oauth2?.scopes.join(" ")}
|
||||||
|
onChange={(e) => handleOAuth2ConfigChange(e, "scopes")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm font-medium">Field mapping</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="identifier">
|
||||||
|
Identifier <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="identifier"
|
||||||
|
type="text"
|
||||||
|
placeholder="The field in the user info response to identify the user"
|
||||||
|
value={state.identityProviderCreate.config?.oauth2?.fieldMapping?.identifier}
|
||||||
|
onChange={(e) => handleFieldMappingChange(e, "identifier")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="display-name">Display name</Label>
|
||||||
|
<Input
|
||||||
|
id="display-name"
|
||||||
|
type="text"
|
||||||
|
placeholder="The field in the user info response to display the user"
|
||||||
|
value={state.identityProviderCreate.config?.oauth2?.fieldMapping?.displayName}
|
||||||
|
onChange={(e) => handleFieldMappingChange(e, "displayName")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</SheetBody>
|
||||||
<Separator />
|
<SheetFooter>
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<p className="text-sm font-medium">Identity provider information</p>
|
|
||||||
{isCreating && (
|
|
||||||
<div className="rounded-md py-2 px-3 bg-secondary text-sm w-full break-all">
|
|
||||||
<span className="text-muted-foreground">Redirect URL</span>
|
|
||||||
<br />
|
|
||||||
<code className="text-xs">{absolutifyLink("/auth/callback")}</code>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="client-id">
|
|
||||||
Client ID <span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="client-id"
|
|
||||||
type="text"
|
|
||||||
placeholder="Client ID of the OAuth2 provider"
|
|
||||||
value={state.identityProviderCreate.config?.oauth2?.clientId}
|
|
||||||
onChange={(e) => handleOAuth2ConfigChange(e, "clientId")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="client-secret">
|
|
||||||
Client Secret <span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="client-secret"
|
|
||||||
type="text"
|
|
||||||
placeholder="Client Secret of the OAuth2 provider"
|
|
||||||
value={state.identityProviderCreate.config?.oauth2?.clientSecret}
|
|
||||||
onChange={(e) => handleOAuth2ConfigChange(e, "clientSecret")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="auth-url">
|
|
||||||
Authorization endpoint <span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="auth-url"
|
|
||||||
type="text"
|
|
||||||
placeholder="Authorization endpoint of the OAuth2 provider"
|
|
||||||
value={state.identityProviderCreate.config?.oauth2?.authUrl}
|
|
||||||
onChange={(e) => handleOAuth2ConfigChange(e, "authUrl")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="token-url">
|
|
||||||
Token endpoint <span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="token-url"
|
|
||||||
type="text"
|
|
||||||
placeholder="Token endpoint of the OAuth2 provider"
|
|
||||||
value={state.identityProviderCreate.config?.oauth2?.tokenUrl}
|
|
||||||
onChange={(e) => handleOAuth2ConfigChange(e, "tokenUrl")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="user-info-url">
|
|
||||||
User endpoint <span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="user-info-url"
|
|
||||||
type="text"
|
|
||||||
placeholder="User endpoint of the OAuth2 provider"
|
|
||||||
value={state.identityProviderCreate.config?.oauth2?.userInfoUrl}
|
|
||||||
onChange={(e) => handleOAuth2ConfigChange(e, "userInfoUrl")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="scopes">
|
|
||||||
Scopes <span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="scopes"
|
|
||||||
type="text"
|
|
||||||
placeholder="Scopes of the OAuth2 provider, separated by space"
|
|
||||||
value={state.identityProviderCreate.config?.oauth2?.scopes.join(" ")}
|
|
||||||
onChange={(e) => handleOAuth2ConfigChange(e, "scopes")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<p className="text-sm font-medium">Field mapping</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="identifier">
|
|
||||||
Identifier <span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="identifier"
|
|
||||||
type="text"
|
|
||||||
placeholder="The field in the user info response to identify the user"
|
|
||||||
value={state.identityProviderCreate.config?.oauth2?.fieldMapping?.identifier}
|
|
||||||
onChange={(e) => handleFieldMappingChange(e, "identifier")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="display-name">Display name</Label>
|
|
||||||
<Input
|
|
||||||
id="display-name"
|
|
||||||
type="text"
|
|
||||||
placeholder="The field in the user info response to display the user"
|
|
||||||
value={state.identityProviderCreate.config?.oauth2?.fieldMapping?.displayName}
|
|
||||||
onChange={(e) => handleFieldMappingChange(e, "displayName")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SheetFooter className="mt-6">
|
|
||||||
<Button variant="outline" disabled={requestState.isLoading} onClick={onClose}>
|
<Button variant="outline" disabled={requestState.isLoading} onClick={onClose}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
import { Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
import { useShortcutStore, useWorkspaceStore } from "@/stores";
|
import { useShortcutStore, useWorkspaceStore } from "@/stores";
|
||||||
@@ -211,163 +211,156 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={true} onOpenChange={onClose}>
|
<Sheet open={true} onOpenChange={onClose}>
|
||||||
<SheetContent className="w-full sm:max-w-md overflow-y-auto">
|
<SheetContent className="w-full sm:max-w-md">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>{isCreating ? "Create Shortcut" : "Edit Shortcut"}</SheetTitle>
|
<SheetTitle>{isCreating ? "Create Shortcut" : "Edit Shortcut"}</SheetTitle>
|
||||||
<SheetDescription>{isCreating ? "Create a new shortcut" : "Edit your shortcut details"}</SheetDescription>
|
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="mt-6 space-y-4">
|
<SheetBody>
|
||||||
<div className="space-y-2">
|
<div className="space-y-4">
|
||||||
<Label htmlFor="name">
|
<div className="space-y-2">
|
||||||
Name <span className="text-destructive">*</span>
|
<Label htmlFor="name">
|
||||||
</Label>
|
Name <span className="text-destructive">*</span>
|
||||||
<div className="flex items-center gap-2">
|
</Label>
|
||||||
<span className="text-sm text-muted-foreground">s/</span>
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm text-muted-foreground">s/</span>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="An easy name to remember"
|
||||||
|
value={state.shortcutCreate.name}
|
||||||
|
onChange={handleNameInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="link">
|
||||||
|
Link <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="link"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="An easy name to remember"
|
placeholder="The destination link of the shortcut"
|
||||||
value={state.shortcutCreate.name}
|
value={state.shortcutCreate.link}
|
||||||
onChange={handleNameInputChange}
|
onChange={handleLinkInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="title">Title</Label>
|
||||||
<div className="space-y-2">
|
<Input
|
||||||
<Label htmlFor="link">
|
id="title"
|
||||||
Link <span className="text-destructive">*</span>
|
type="text"
|
||||||
</Label>
|
placeholder="The title of the shortcut"
|
||||||
<Input
|
value={state.shortcutCreate.title}
|
||||||
id="link"
|
onChange={handleTitleInputChange}
|
||||||
type="text"
|
/>
|
||||||
placeholder="The destination link of the shortcut"
|
</div>
|
||||||
value={state.shortcutCreate.link}
|
<div className="space-y-2">
|
||||||
onChange={handleLinkInputChange}
|
<Label htmlFor="description">Description</Label>
|
||||||
/>
|
<Input
|
||||||
</div>
|
id="description"
|
||||||
|
type="text"
|
||||||
<div className="space-y-2">
|
placeholder="A short description of the shortcut"
|
||||||
<Label htmlFor="title">Title</Label>
|
value={state.shortcutCreate.description}
|
||||||
<Input
|
onChange={handleDescriptionInputChange}
|
||||||
id="title"
|
/>
|
||||||
type="text"
|
</div>
|
||||||
placeholder="The title of the shortcut"
|
<div className="space-y-2">
|
||||||
value={state.shortcutCreate.title}
|
<Label htmlFor="tags">Tags</Label>
|
||||||
onChange={handleTitleInputChange}
|
<Input id="tags" type="text" placeholder="The tags of shortcut" value={tag} onChange={handleTagsInputChange} />
|
||||||
/>
|
{tagSuggestions.length > 0 && (
|
||||||
</div>
|
<div className="flex flex-row items-start gap-2 mt-2">
|
||||||
|
<Icon.Asterisk className="w-4 h-auto shrink-0 mt-0.5 text-muted-foreground" />
|
||||||
<div className="space-y-2">
|
<div className="flex flex-row flex-wrap gap-2">
|
||||||
<Label htmlFor="description">Description</Label>
|
{tagSuggestions.map((tag) => (
|
||||||
<Input
|
<span
|
||||||
id="description"
|
className="text-muted-foreground cursor-pointer text-sm hover:text-foreground transition-colors"
|
||||||
type="text"
|
key={tag}
|
||||||
placeholder="A short description of the shortcut"
|
onClick={() => handleTagSuggestionsClick(tag)}
|
||||||
value={state.shortcutCreate.description}
|
>
|
||||||
onChange={handleDescriptionInputChange}
|
{tag}
|
||||||
/>
|
</span>
|
||||||
</div>
|
))}
|
||||||
|
</div>
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="tags">Tags</Label>
|
|
||||||
<Input id="tags" type="text" placeholder="The tags of shortcut" value={tag} onChange={handleTagsInputChange} />
|
|
||||||
{tagSuggestions.length > 0 && (
|
|
||||||
<div className="flex flex-row items-start gap-2 mt-2">
|
|
||||||
<Icon.Asterisk className="w-4 h-auto shrink-0 mt-0.5 text-muted-foreground" />
|
|
||||||
<div className="flex flex-row flex-wrap gap-2">
|
|
||||||
{tagSuggestions.map((tag) => (
|
|
||||||
<span
|
|
||||||
className="text-muted-foreground cursor-pointer text-sm hover:text-foreground transition-colors"
|
|
||||||
key={tag}
|
|
||||||
onClick={() => handleTagSuggestionsClick(tag)}
|
|
||||||
>
|
|
||||||
{tag}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="public"
|
|
||||||
checked={state.shortcutCreate.visibility === Visibility.PUBLIC}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
setPartialState({
|
|
||||||
shortcutCreate: Object.assign(state.shortcutCreate, {
|
|
||||||
visibility: checked ? Visibility.PUBLIC : Visibility.WORKSPACE,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="public" className="text-sm font-normal cursor-pointer">
|
|
||||||
{t(`shortcut.visibility.public.description`)}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator className="my-4" />
|
|
||||||
|
|
||||||
<div className="border rounded-lg overflow-hidden">
|
|
||||||
<div
|
|
||||||
className={classnames(
|
|
||||||
"flex flex-row justify-between items-center px-3 py-2 cursor-pointer hover:bg-accent transition-colors",
|
|
||||||
showOpenGraphMetadata && "bg-accent border-b",
|
|
||||||
)}
|
)}
|
||||||
onClick={() => setShowOpenGraphMetadata(!showOpenGraphMetadata)}
|
|
||||||
>
|
|
||||||
<span className="text-sm flex items-center gap-1">
|
|
||||||
Social media metadata
|
|
||||||
<Icon.Sparkles className="w-4 h-auto text-primary" />
|
|
||||||
</span>
|
|
||||||
<Icon.ChevronDown
|
|
||||||
className={classnames("w-4 h-auto text-muted-foreground transition-transform", showOpenGraphMetadata && "rotate-180")}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{showOpenGraphMetadata && (
|
<div className="flex items-center space-x-2">
|
||||||
<div className="p-3 space-y-4">
|
<Checkbox
|
||||||
<div className="space-y-2">
|
id="public"
|
||||||
<Label htmlFor="og-image" className="text-sm">
|
checked={state.shortcutCreate.visibility === Visibility.PUBLIC}
|
||||||
Image URL
|
onCheckedChange={(checked) =>
|
||||||
</Label>
|
setPartialState({
|
||||||
<Input
|
shortcutCreate: Object.assign(state.shortcutCreate, {
|
||||||
id="og-image"
|
visibility: checked ? Visibility.PUBLIC : Visibility.WORKSPACE,
|
||||||
type="text"
|
}),
|
||||||
placeholder="https://the.link.to/the/image.png"
|
})
|
||||||
value={state.shortcutCreate.ogMetadata?.image}
|
}
|
||||||
onChange={handleOpenGraphMetadataImageChange}
|
/>
|
||||||
/>
|
<Label htmlFor="public" className="text-sm font-normal cursor-pointer">
|
||||||
</div>
|
{t(`shortcut.visibility.public.description`)}
|
||||||
<div className="space-y-2">
|
</Label>
|
||||||
<Label htmlFor="og-title" className="text-sm">
|
</div>
|
||||||
Title
|
<Separator className="my-4" />
|
||||||
</Label>
|
<div className="border rounded-lg overflow-hidden">
|
||||||
<Input
|
<div
|
||||||
id="og-title"
|
className={classnames(
|
||||||
type="text"
|
"flex flex-row justify-between items-center px-3 py-2 cursor-pointer hover:bg-accent transition-colors",
|
||||||
placeholder="Slash - An open source, self-hosted platform"
|
showOpenGraphMetadata && "bg-accent border-b",
|
||||||
value={state.shortcutCreate.ogMetadata?.title}
|
)}
|
||||||
onChange={handleOpenGraphMetadataTitleChange}
|
onClick={() => setShowOpenGraphMetadata(!showOpenGraphMetadata)}
|
||||||
/>
|
>
|
||||||
</div>
|
<span className="text-sm flex items-center gap-1">
|
||||||
<div className="space-y-2">
|
Social media metadata
|
||||||
<Label htmlFor="og-description" className="text-sm">
|
<Icon.Sparkles className="w-4 h-auto text-primary" />
|
||||||
Description
|
</span>
|
||||||
</Label>
|
<Icon.ChevronDown
|
||||||
<Textarea
|
className={classnames("w-4 h-auto text-muted-foreground transition-transform", showOpenGraphMetadata && "rotate-180")}
|
||||||
id="og-description"
|
/>
|
||||||
placeholder="An open source, self-hosted platform for sharing and managing your most frequently used links."
|
|
||||||
rows={3}
|
|
||||||
value={state.shortcutCreate.ogMetadata?.description}
|
|
||||||
onChange={handleOpenGraphMetadataDescriptionChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{showOpenGraphMetadata && (
|
||||||
|
<div className="p-3 space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="og-image" className="text-sm">
|
||||||
|
Image URL
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="og-image"
|
||||||
|
type="text"
|
||||||
|
placeholder="https://the.link.to/the/image.png"
|
||||||
|
value={state.shortcutCreate.ogMetadata?.image}
|
||||||
|
onChange={handleOpenGraphMetadataImageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="og-title" className="text-sm">
|
||||||
|
Title
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="og-title"
|
||||||
|
type="text"
|
||||||
|
placeholder="Slash - An open source, self-hosted platform"
|
||||||
|
value={state.shortcutCreate.ogMetadata?.title}
|
||||||
|
onChange={handleOpenGraphMetadataTitleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="og-description" className="text-sm">
|
||||||
|
Description
|
||||||
|
</Label>
|
||||||
|
<Textarea
|
||||||
|
id="og-description"
|
||||||
|
placeholder="An open source, self-hosted platform for sharing and managing your most frequently used links."
|
||||||
|
rows={3}
|
||||||
|
value={state.shortcutCreate.ogMetadata?.description}
|
||||||
|
onChange={handleOpenGraphMetadataDescriptionChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SheetBody>
|
||||||
|
<SheetFooter>
|
||||||
<SheetFooter className="mt-6">
|
|
||||||
<Button variant="outline" onClick={onClose} disabled={requestState.isLoading}>
|
<Button variant="outline" onClick={onClose} disabled={requestState.isLoading}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const SheetOverlay = React.forwardRef<
|
|||||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
const sheetVariants = cva(
|
const sheetVariants = cva(
|
||||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 flex flex-col overflow-hidden",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
side: {
|
side: {
|
||||||
@@ -64,12 +64,17 @@ const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Con
|
|||||||
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
||||||
|
|
||||||
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
|
<div className={cn("flex flex-col space-y-2 text-center sm:text-left flex-shrink-0", className)} {...props} />
|
||||||
);
|
);
|
||||||
SheetHeader.displayName = "SheetHeader";
|
SheetHeader.displayName = "SheetHeader";
|
||||||
|
|
||||||
|
const SheetBody = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div className={cn("flex-1 overflow-y-auto min-h-0", className)} {...props} />
|
||||||
|
);
|
||||||
|
SheetBody.displayName = "SheetBody";
|
||||||
|
|
||||||
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
|
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 pt-4 flex-shrink-0", className)} {...props} />
|
||||||
);
|
);
|
||||||
SheetFooter.displayName = "SheetFooter";
|
SheetFooter.displayName = "SheetFooter";
|
||||||
|
|
||||||
@@ -89,4 +94,16 @@ const SheetDescription = React.forwardRef<
|
|||||||
));
|
));
|
||||||
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
||||||
|
|
||||||
export { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription };
|
export {
|
||||||
|
Sheet,
|
||||||
|
SheetPortal,
|
||||||
|
SheetOverlay,
|
||||||
|
SheetTrigger,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetHeader,
|
||||||
|
SheetBody,
|
||||||
|
SheetFooter,
|
||||||
|
SheetTitle,
|
||||||
|
SheetDescription,
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user