Credentials page UX improvement (#3941)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useEffect } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { CardStackIcon, PlusIcon } from "@radix-ui/react-icons";
|
import { CardStackIcon, PlusIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
@@ -16,13 +16,36 @@ import {
|
|||||||
import { KeyIcon } from "@/components/icons/KeyIcon";
|
import { KeyIcon } from "@/components/icons/KeyIcon";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { CredentialsTotpTab } from "./CredentialsTotpTab";
|
import { CredentialsTotpTab } from "./CredentialsTotpTab";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
const subHeaderText =
|
const subHeaderText =
|
||||||
"Securely store your passwords, credit cards, and manage incoming 2FA codes for your workflows.";
|
"Securely store your passwords, credit cards, and manage incoming 2FA codes for your workflows.";
|
||||||
|
|
||||||
|
const TAB_VALUES = ["passwords", "creditCards", "twoFactor"] as const;
|
||||||
|
type TabValue = (typeof TAB_VALUES)[number];
|
||||||
|
const DEFAULT_TAB: TabValue = "passwords";
|
||||||
|
|
||||||
function CredentialsPage() {
|
function CredentialsPage() {
|
||||||
const { setIsOpen, setType } = useCredentialModalState();
|
const { setIsOpen, setType } = useCredentialModalState();
|
||||||
const [activeTab, setActiveTab] = useState("passwords");
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const tabParam = searchParams.get("tab");
|
||||||
|
const matchedTab = TAB_VALUES.find((tab) => tab === tabParam);
|
||||||
|
const activeTab: TabValue = matchedTab ?? DEFAULT_TAB;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tabParam && !matchedTab) {
|
||||||
|
const params = new URLSearchParams(searchParams);
|
||||||
|
params.set("tab", DEFAULT_TAB);
|
||||||
|
setSearchParams(params, { replace: true });
|
||||||
|
}
|
||||||
|
}, [tabParam, matchedTab, searchParams, setSearchParams]);
|
||||||
|
|
||||||
|
function handleTabChange(value: string) {
|
||||||
|
const nextTab = TAB_VALUES.find((tab) => tab === value) ?? DEFAULT_TAB;
|
||||||
|
const params = new URLSearchParams(searchParams);
|
||||||
|
params.set("tab", nextTab);
|
||||||
|
setSearchParams(params, { replace: true });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
@@ -60,9 +83,9 @@ function CredentialsPage() {
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultValue="passwords"
|
value={activeTab}
|
||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
onValueChange={setActiveTab}
|
onValueChange={handleTabChange}
|
||||||
>
|
>
|
||||||
<TabsList className="bg-slate-elevation1">
|
<TabsList className="bg-slate-elevation1">
|
||||||
<TabsTrigger value="passwords">Passwords</TabsTrigger>
|
<TabsTrigger value="passwords">Passwords</TabsTrigger>
|
||||||
|
|||||||
@@ -3290,7 +3290,7 @@ class AgentDB:
|
|||||||
self,
|
self,
|
||||||
organization_id: str,
|
organization_id: str,
|
||||||
limit: int = 50,
|
limit: int = 50,
|
||||||
valid_lifespan_minutes: int = settings.TOTP_LIFESPAN_MINUTES,
|
valid_lifespan_minutes: int | None = None,
|
||||||
otp_type: OTPType | None = None,
|
otp_type: OTPType | None = None,
|
||||||
workflow_run_id: str | None = None,
|
workflow_run_id: str | None = None,
|
||||||
) -> list[TOTPCode]:
|
) -> list[TOTPCode]:
|
||||||
@@ -3304,11 +3304,13 @@ class AgentDB:
|
|||||||
TOTPCodeModel.workflow_run_id.is_(None),
|
TOTPCodeModel.workflow_run_id.is_(None),
|
||||||
)
|
)
|
||||||
async with self.Session() as session:
|
async with self.Session() as session:
|
||||||
query = (
|
query = select(TOTPCodeModel).filter_by(organization_id=organization_id)
|
||||||
select(TOTPCodeModel)
|
|
||||||
.filter_by(organization_id=organization_id)
|
if valid_lifespan_minutes is not None:
|
||||||
.filter(TOTPCodeModel.created_at > datetime.utcnow() - timedelta(minutes=valid_lifespan_minutes))
|
query = query.filter(
|
||||||
)
|
TOTPCodeModel.created_at > datetime.utcnow() - timedelta(minutes=valid_lifespan_minutes)
|
||||||
|
)
|
||||||
|
|
||||||
if otp_type:
|
if otp_type:
|
||||||
query = query.filter(TOTPCodeModel.otp_type == otp_type)
|
query = query.filter(TOTPCodeModel.otp_type == otp_type)
|
||||||
if workflow_run_id is not None:
|
if workflow_run_id is not None:
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ async def get_totp_codes(
|
|||||||
codes = await app.DATABASE.get_recent_otp_codes(
|
codes = await app.DATABASE.get_recent_otp_codes(
|
||||||
organization_id=curr_org.organization_id,
|
organization_id=curr_org.organization_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
|
valid_lifespan_minutes=None,
|
||||||
otp_type=otp_type,
|
otp_type=otp_type,
|
||||||
workflow_run_id=workflow_run_id,
|
workflow_run_id=workflow_run_id,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user