Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
678202c
refactor: dialog container
ogzhanolguncu Apr 18, 2025
1d75f87
feat: add layout
ogzhanolguncu Apr 18, 2025
896560d
feat: add general setup form
ogzhanolguncu Apr 18, 2025
3e1155b
feat: add step validation
ogzhanolguncu Apr 18, 2025
1a353da
feat: add ratelimit step
ogzhanolguncu Apr 18, 2025
20cde15
feat: add usage limit
ogzhanolguncu Apr 18, 2025
096b3ee
feat: expiration
ogzhanolguncu Apr 18, 2025
622aa58
chore: run fmt
ogzhanolguncu Apr 18, 2025
c4ef986
feat: add metadata
ogzhanolguncu Apr 18, 2025
4c0ef46
fix: schema structure
ogzhanolguncu Apr 18, 2025
be44c3d
refactor: remove unused codes
ogzhanolguncu Apr 18, 2025
011589c
refactor: form names
ogzhanolguncu Apr 18, 2025
b9192f3
refactor: make dialog component more composable
ogzhanolguncu Apr 21, 2025
4936df0
refactor: persist form for accidental closes
ogzhanolguncu Apr 21, 2025
0d1edb3
fix: ref issue
ogzhanolguncu Apr 21, 2025
746d500
fix: add validation after loading persisted state
ogzhanolguncu Apr 21, 2025
ce08839
feat: add key submit
ogzhanolguncu Apr 21, 2025
1e1b617
refactor: move validation logic to hook
ogzhanolguncu Apr 21, 2025
f488658
feat: add final create key screen
ogzhanolguncu Apr 21, 2025
454b216
fix: wrong payload
ogzhanolguncu Apr 22, 2025
a7c77e5
fix: undefined issues
ogzhanolguncu Apr 22, 2025
1dc2f70
fix: credits structure
ogzhanolguncu Apr 22, 2025
30ea84b
Merge branch 'main' into keys-create-menu
ogzhanolguncu Apr 22, 2025
94b409a
fix: type
ogzhanolguncu Apr 22, 2025
5b5fe59
fix: coderabbit isues
ogzhanolguncu Apr 22, 2025
c219dae
fix: coderabbit fix
ogzhanolguncu Apr 22, 2025
b591fc3
feat: add final confirmation
ogzhanolguncu Apr 22, 2025
4ba872f
fix: coderabbit issue
ogzhanolguncu Apr 22, 2025
496c12e
fix: move persisted hook to global
ogzhanolguncu Apr 25, 2025
f5bbb2a
feat: add multiple ratelimits to setup
ogzhanolguncu Apr 25, 2025
4bf7030
refactor: improve error handling
ogzhanolguncu Apr 25, 2025
a37ccfb
fix: style issues
ogzhanolguncu Apr 25, 2025
ceda977
Merge branch 'main' of github.com:unkeyed/unkey into keys-create-menu
ogzhanolguncu Apr 25, 2025
241b58d
chore: run fmt
ogzhanolguncu Apr 25, 2025
284a7e7
fix: add ratelimit creation
ogzhanolguncu Apr 28, 2025
9346cc4
feat: make the schema SSOT for key creation
ogzhanolguncu Apr 28, 2025
8142cb0
refactor: add proper namespacing
ogzhanolguncu Apr 28, 2025
d3ff0ec
fix: vercel comments
ogzhanolguncu Apr 28, 2025
5f5e91c
fix: replace ownerId with external ID
ogzhanolguncu Apr 28, 2025
ae7d42c
fix: base ui components stylings
ogzhanolguncu Apr 28, 2025
c02ae4d
fix: json field set
ogzhanolguncu Apr 28, 2025
2032a5a
fix: create another button
ogzhanolguncu Apr 28, 2025
a91897d
Merge branch 'main' into keys-create-menu
ogzhanolguncu Apr 28, 2025
14c511a
fix: expiration issues
ogzhanolguncu Apr 28, 2025
594e8a5
fix: expiration issues
ogzhanolguncu Apr 28, 2025
f91fe79
fix: type issue
ogzhanolguncu Apr 28, 2025
c3690b7
fix: use 10 minute as our buffer expiration
ogzhanolguncu Apr 28, 2025
7fae93e
feat: add popover for action menu items
ogzhanolguncu Apr 28, 2025
f3ec103
fix: icons
ogzhanolguncu Apr 29, 2025
d50f320
feat: add edit keyname
ogzhanolguncu Apr 29, 2025
bbb195a
feat: finalize edit key name
ogzhanolguncu Apr 29, 2025
f4df021
feat: finalize key id
ogzhanolguncu Apr 29, 2025
996ce22
feat: add disable key dialog
ogzhanolguncu Apr 29, 2025
44bba83
feat: finalize update key status
ogzhanolguncu Apr 29, 2025
a9a505a
feat: add remaining uses
ogzhanolguncu Apr 29, 2025
23b0602
feat: add working credits section
ogzhanolguncu Apr 29, 2025
b4e74bc
feat: fix persistence issue
ogzhanolguncu Apr 29, 2025
e41e85b
Merge branch 'main' of github.com:unkeyed/unkey into keys-action-menu
ogzhanolguncu Apr 29, 2025
4487263
refactor: use same credits form from create key dialog
ogzhanolguncu Apr 30, 2025
47dc252
refactor: reorganize api result
ogzhanolguncu Apr 30, 2025
bc7a551
feat: add ratelimit updates/creates/deletion to menu action
ogzhanolguncu Apr 30, 2025
804409e
feat: add expiration
ogzhanolguncu Apr 30, 2025
748649a
feat: add metadata
ogzhanolguncu Apr 30, 2025
d5572fb
feat: add key delete
ogzhanolguncu Apr 30, 2025
1bd996c
fix: table issues
ogzhanolguncu Apr 30, 2025
faf6867
chore: revert file location
ogzhanolguncu May 2, 2025
3f3fd92
feat: add external id
ogzhanolguncu May 2, 2025
88812c7
refactor: bring back combobox
ogzhanolguncu May 2, 2025
b272026
feat: finalize combobox
ogzhanolguncu May 2, 2025
71274d8
feat: enable toggling external id
ogzhanolguncu May 2, 2025
b8e9152
fix: add old updateOwnerId
ogzhanolguncu May 2, 2025
09686e2
feat: make generic confirmation popover
ogzhanolguncu May 5, 2025
a52bc70
fix: checkbox style issues
ogzhanolguncu May 5, 2025
6fc9f90
fix: styling inconsistencies
ogzhanolguncu May 5, 2025
441f750
refactor: use same identitiy component for external id create
ogzhanolguncu May 5, 2025
cd5c656
fix: validation issues
ogzhanolguncu May 5, 2025
7425aa7
feat: add confirmations
ogzhanolguncu May 5, 2025
f42d198
Merge branch 'main' of github.com:unkeyed/unkey into keys-action-menu
ogzhanolguncu May 5, 2025
654ed50
fix: any type
ogzhanolguncu May 5, 2025
117ca3d
fix: disable key
ogzhanolguncu May 5, 2025
903891a
fix: navbar
ogzhanolguncu May 5, 2025
ff99d48
Merge branch 'main' of github.com:unkeyed/unkey into keys-action-menu
ogzhanolguncu May 5, 2025
311f140
feat: add identity creation via edit
ogzhanolguncu May 6, 2025
db1c919
refactor: make reusable component for external id field
ogzhanolguncu May 6, 2025
6f1826a
Merge branch 'main' of github.com:unkeyed/unkey into keys-action-menu
ogzhanolguncu May 6, 2025
19a4dbd
feat: add better dialog
ogzhanolguncu May 6, 2025
a5f2ddb
fix: overflow issue
ogzhanolguncu May 6, 2025
5312d5a
Merge branch 'main' of github.com:unkeyed/unkey into keys-action-menu
ogzhanolguncu May 6, 2025
8e61d13
fix: only show action button bg on hover
ogzhanolguncu May 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,44 +29,64 @@ export const UsageSetup = () => {
const handleSwitchChange = (checked: boolean) => {
setValue("limit.enabled", checked);

// When enabling, ensure refill has the correct structure
if (checked && !getValues("limit.data.refill.interval")) {
setValue("limit.data.refill.interval", "none", { shouldValidate: true });
setValue("limit.data.refill.amount", undefined, { shouldValidate: true });
setValue("limit.data.refill.refillDay", undefined, {
shouldValidate: true,
});
// When enabling, ensure default values are set properly
if (checked) {
// Set default remaining to 100 if not already set
if (!getValues("limit.data.remaining")) {
setValue("limit.data.remaining", 100, { shouldValidate: true });
}

// Set up refill structure with defaults, using the entire object at once
if (!getValues("limit.data.refill.interval")) {
setValue(
"limit.data.refill",
{
interval: "none",
amount: undefined,
refillDay: undefined,
},
{ shouldValidate: true },
);
}
}

trigger("limit");
};

const handleRefillIntervalChange = (value: "none" | "daily" | "monthly") => {
setValue("limit.data.refill.interval", value, { shouldValidate: true });

// Clean up related fields based on the selected interval
if (value === "none") {
setValue("limit.data.refill.amount", undefined, { shouldValidate: true });
setValue("limit.data.refill.refillDay", undefined, {
shouldValidate: true,
});
// For "none", set entire refill object
setValue(
"limit.data.refill",
{
interval: "none",
amount: undefined,
refillDay: undefined,
},
{ shouldValidate: true },
);
} else if (value === "daily") {
// For daily, ensure refillDay is undefined but keep amount if present
setValue("limit.data.refill.refillDay", undefined, {
shouldValidate: true,
});
// If amount is not set, set a default
if (!getValues("limit.data.refill.amount")) {
setValue("limit.data.refill.amount", 100, { shouldValidate: true });
}
// For "daily"
setValue(
"limit.data.refill",
{
interval: "daily",
amount: getValues("limit.data.refill.amount") || 100,
refillDay: undefined, // Must be undefined for daily
},
{ shouldValidate: true },
);
} else if (value === "monthly") {
// For monthly, ensure both amount and refillDay have values
if (!getValues("limit.data.refill.amount")) {
setValue("limit.data.refill.amount", 100, { shouldValidate: true });
}
if (!getValues("limit.data.refill.refillDay")) {
setValue("limit.data.refill.refillDay", 1, { shouldValidate: true });
}
// For "monthly"
setValue(
"limit.data.refill",
{
interval: "monthly",
amount: getValues("limit.data.refill.amount") || 100,
refillDay: getValues("limit.data.refill.refillDay") || 1,
},
{ shouldValidate: true },
);
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useCreateIdentity } from "@/app/(app)/apis/[apiId]/_components/create-key/hooks/use-create-identity";
import { useFetchIdentities } from "@/app/(app)/apis/[apiId]/_components/create-key/hooks/use-fetch-identities";
import { createIdentityOptions } from "@/app/(app)/apis/[apiId]/_components/create-key/hooks/use-fetch-identities/create-identity-options";
import { FormCombobox } from "@/components/ui/form-combobox";
import { TriangleWarning2 } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { cn } from "@unkey/ui/src/lib/utils";
import { useState } from "react";

type ExternalIdFieldProps = {
value: string | null;
onChange: (id: string | null) => void;
error?: string;
disabled?: boolean;
};

export const ExternalIdField = ({
value,
onChange,
error,
disabled = false,
}: ExternalIdFieldProps) => {
const [searchValue, setSearchValue] = useState("");

const { identities, isFetchingNextPage, hasNextPage, loadMore } = useFetchIdentities();
const identityOptions = createIdentityOptions({
identities,
hasNextPage,
isFetchingNextPage,
loadMore,
});

const createIdentity = useCreateIdentity((data) => {
onChange(data.identityId);
});

const handleCreateIdentity = () => {
if (searchValue.trim()) {
createIdentity.mutate({
externalId: searchValue.trim(),
meta: null,
});
}
};

return (
<FormCombobox
optional
label="External ID"
description="ID of the user/workspace in your system for key attribution."
options={identityOptions}
// This will force close popover whenever new item added or selected.
key={value}
value={value || ""}
onChange={(e) => setSearchValue(e.currentTarget.value)}
onSelect={(val) => {
const identity = identities.find((id) => id.id === val);
onChange(identity?.id || null);
}}
placeholder={
<div className="flex w-full text-grayA-8 text-xs gap-1.5 items-center py-2">
Select External ID
</div>
}
searchPlaceholder="Search External ID..."
emptyMessage={
<div className="p-0 max-w-[460px]">
<div className="px-3 py-3 w-full">
<div className="flex gap-2 items-center justify-start">
<div
className={cn(
"flex items-center rounded size-5 justify-center",
"bg-warningA-4",
"text-warning-11",
)}
>
<TriangleWarning2 size="sm-regular" />
</div>
<div className="font-medium text-[13px] leading-7 text-gray-12">
External ID not found
</div>
</div>
</div>
<div className="w-full">
<div className="h-[1px] bg-grayA-3 w-full" />
</div>
<div className="px-4 w-full text-gray-11 text-[13px] leading-6 my-4 text-left">
You can create a new identity with this <span className="font-medium">External ID</span>{" "}
and connect it <span className="font-medium">immediately</span>.
</div>
<div className="w-full px-4 pb-4">
<Button
type="button"
variant="primary"
size="xlg"
className="rounded-lg w-full"
onClick={handleCreateIdentity}
loading={createIdentity.isLoading}
disabled={!searchValue.trim() || createIdentity.isLoading || disabled}
>
Create
</Button>
</div>
</div>
}
variant="default"
error={error}
disabled={disabled}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"use client";
import { FormInput } from "@unkey/ui";
import { useFormContext } from "react-hook-form";
import { Controller, useFormContext } from "react-hook-form";
import type { FormValues } from "../create-key.schema";
import { ExternalIdField } from "./external-id-field";

export const GeneralSetup = () => {
const {
register,
formState: { errors },
control,
} = useFormContext<FormValues>();

return (
Expand All @@ -32,15 +34,17 @@ export const GeneralSetup = () => {
optional
{...register("prefix")}
/>
<FormInput
className="[&_input:first-of-type]:h-[36px]"
label="External ID"
maxLength={256}
placeholder="Enter external ID"
description="ID of the user/workspace in your system for key attribution."
error={errors.externalId?.message}
optional
{...register("externalId")}
<Controller
name="externalId"
control={control}
defaultValue=""
render={({ field }) => (
<ExternalIdField
value={field.value ?? null}
onChange={field.onChange}
error={errors.externalId?.message}
/>
)}
/>
<FormInput
className="[&_input:first-of-type]:h-[36px]"
Expand All @@ -55,18 +59,6 @@ export const GeneralSetup = () => {
maxLength={3}
{...register("bytes")}
/>

{/* INFO: We'll enable that soon */}
{/* <FormInput */}
{/* className="[&_input:first-of-type]:h-[36px]" */}
{/* label="Environments" */}
{/* maxLength={256} */}
{/* placeholder="Enter environment (e.g. test, dev, prod)" */}
{/* description="Environment label to separate keys (e.g. test, live)." */}
{/* error={errors.environment?.message} */}
{/* optional */}
{/* {...register("environment")} */}
{/* /> */}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
"use client";

import { RatelimitOverviewTooltip } from "@/app/(app)/ratelimits/[namespaceId]/_overview/components/table/components/ratelimit-overview-tooltip";
import { ConfirmPopover } from "@/components/confirmation-popover";
import { CopyButton } from "@/components/dashboard/copy-button";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Popover, PopoverContent } from "@/components/ui/popover";
import { toast } from "@/components/ui/toaster";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import {
ArrowRight,
Check,
CircleInfo,
Eye,
EyeSlash,
Key2,
Plus,
TriangleWarning2,
} from "@unkey/icons";
import { ArrowRight, Check, CircleInfo, Eye, EyeSlash, Key2, Plus } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { useRef, useState } from "react";
import { UNNAMED_KEY } from "../create-key.constants";
import { SecretKey } from "./secret-key";

const PopoverAnchor = PopoverPrimitive.Anchor;
const PopoverClose = PopoverPrimitive.Close;

export const KeyCreatedSuccessDialog = ({
isOpen,
onClose,
Expand All @@ -42,7 +30,6 @@ export const KeyCreatedSuccessDialog = ({
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
const [isCreateAnother, setIsCreateAnother] = useState(false);
const xButtonRef = useRef<HTMLButtonElement>(null);
const shouldShowWarning = true;

if (!keyData) {
return null;
Expand Down Expand Up @@ -87,8 +74,8 @@ export const KeyCreatedSuccessDialog = ({
>
<DialogContent
className="drop-shadow-2xl border-gray-4 overflow-hidden !rounded-2xl p-0 gap-0 min-w-[760px] max-h-[90vh] overflow-y-auto"
showCloseWarning={shouldShowWarning}
onAttemptClose={shouldShowWarning ? handleAttemptClose : undefined}
showCloseWarning
onAttemptClose={handleAttemptClose}
xButtonRef={xButtonRef}
>
<>
Expand Down Expand Up @@ -139,7 +126,7 @@ export const KeyCreatedSuccessDialog = ({
disabled={!keyData.name}
>
<div className="text-accent-9 text-xs max-w-[160px] truncate">
{keyData.name ?? "Unnamed Key"}
{keyData.name ?? UNNAMED_KEY}
</div>
</RatelimitOverviewTooltip>
</div>
Expand Down Expand Up @@ -238,42 +225,21 @@ export const KeyCreatedSuccessDialog = ({
</div>
</div>
</div>

<Popover open={isConfirmOpen} onOpenChange={setIsConfirmOpen}>
<PopoverAnchor virtualRef={xButtonRef} />
<PopoverContent
sideOffset={5}
className="bg-white dark:bg-black flex flex-col items-center justify-center border-grayA-4 overflow-hidden !rounded-[10px] p-0 gap-0 min-w-[344px]"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<div className="p-4 w-full">
<div className="flex gap-4 items-center justify-start">
<div className="bg-warningA-4 text-warning-11 flex items-center justify-center rounded size-[22px]">
<TriangleWarning2 size="sm-regular" />
</div>
<div className="font-medium text-[13px] leading-7 text-gray-12">
You won't see this secret key again!
</div>
</div>
</div>
<div className="w-full">
<div className="h-[1px] bg-grayA-3 w-full" />
</div>
<div className="px-4 w-full text-gray-11 text-[13px] leading-6 my-4">
Make sure to copy your secret key before closing. It cannot be retrieved later.
</div>
<div className="space-x-3 w-full px-4 pb-4">
<Button color="warning" onClick={handleConfirmAndClose}>
Close anyway
</Button>
<PopoverClose asChild>
<Button variant="ghost" className="text-gray-9">
Dismiss
</Button>
</PopoverClose>
</div>
</PopoverContent>
</Popover>
<ConfirmPopover
isOpen={isConfirmOpen}
onOpenChange={setIsConfirmOpen}
onConfirm={handleConfirmAndClose}
triggerRef={xButtonRef}
title="You won't see this secret key again!"
description="Make sure to copy your secret key before closing. It cannot be retrieved later."
confirmButtonText="Close anyway"
cancelButtonText="Dismiss"
variant="warning"
popoverProps={{
sideOffset: 5,
onOpenAutoFocus: (e) => e.preventDefault(),
}}
/>
</>
</DialogContent>
</Dialog>
Expand Down
Loading
Loading