feat: enhance team account creation with policy checks and UI updates (#436)
* feat: enhance team account creation with policy checks and UI updates
This commit is contained in:
committed by
GitHub
parent
ab57b24518
commit
5237d34e6f
@@ -19,10 +19,12 @@ import { loadUserWorkspace } from '../_lib/server/load-user-workspace';
|
|||||||
import { HomeAddAccountButton } from './home-add-account-button';
|
import { HomeAddAccountButton } from './home-add-account-button';
|
||||||
|
|
||||||
export function HomeAccountsList() {
|
export function HomeAccountsList() {
|
||||||
const { accounts } = use(loadUserWorkspace());
|
const { accounts, canCreateTeamAccount } = use(loadUserWorkspace());
|
||||||
|
|
||||||
if (!accounts.length) {
|
if (!accounts.length) {
|
||||||
return <HomeAccountsListEmptyState />;
|
return (
|
||||||
|
<HomeAccountsListEmptyState canCreateTeamAccount={canCreateTeamAccount} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -42,12 +44,17 @@ export function HomeAccountsList() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HomeAccountsListEmptyState() {
|
function HomeAccountsListEmptyState(props: {
|
||||||
|
canCreateTeamAccount: { allowed: boolean; reason?: string };
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={'flex flex-1'}>
|
<div className={'flex flex-1'}>
|
||||||
<EmptyState>
|
<EmptyState>
|
||||||
<EmptyStateButton asChild>
|
<EmptyStateButton asChild>
|
||||||
<HomeAddAccountButton className={'mt-4'} />
|
<HomeAddAccountButton
|
||||||
|
className={'mt-4'}
|
||||||
|
canCreateTeamAccount={props.canCreateTeamAccount}
|
||||||
|
/>
|
||||||
</EmptyStateButton>
|
</EmptyStateButton>
|
||||||
<EmptyStateHeading>
|
<EmptyStateHeading>
|
||||||
<Trans i18nKey={'account:noTeamsYet'} />
|
<Trans i18nKey={'account:noTeamsYet'} />
|
||||||
|
|||||||
@@ -4,19 +4,54 @@ import { useState } from 'react';
|
|||||||
|
|
||||||
import { CreateTeamAccountDialog } from '@kit/team-accounts/components';
|
import { CreateTeamAccountDialog } from '@kit/team-accounts/components';
|
||||||
import { Button } from '@kit/ui/button';
|
import { Button } from '@kit/ui/button';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@kit/ui/tooltip';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
export function HomeAddAccountButton(props: { className?: string }) {
|
interface HomeAddAccountButtonProps {
|
||||||
|
className?: string;
|
||||||
|
canCreateTeamAccount?: {
|
||||||
|
allowed: boolean;
|
||||||
|
reason?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HomeAddAccountButton(props: HomeAddAccountButtonProps) {
|
||||||
const [isAddingAccount, setIsAddingAccount] = useState(false);
|
const [isAddingAccount, setIsAddingAccount] = useState(false);
|
||||||
|
|
||||||
|
const canCreate = props.canCreateTeamAccount?.allowed ?? true;
|
||||||
|
const reason = props.canCreateTeamAccount?.reason;
|
||||||
|
|
||||||
|
const button = (
|
||||||
|
<Button
|
||||||
|
className={props.className}
|
||||||
|
onClick={() => setIsAddingAccount(true)}
|
||||||
|
disabled={!canCreate}
|
||||||
|
>
|
||||||
|
<Trans i18nKey={'account:createTeamButtonLabel'} />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
{!canCreate && reason ? (
|
||||||
className={props.className}
|
<TooltipProvider>
|
||||||
onClick={() => setIsAddingAccount(true)}
|
<Tooltip>
|
||||||
>
|
<TooltipTrigger asChild>
|
||||||
<Trans i18nKey={'account:createTeamButtonLabel'} />
|
<span className="cursor-not-allowed">{button}</span>
|
||||||
</Button>
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<Trans i18nKey={reason} defaults={reason} />
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
) : (
|
||||||
|
button
|
||||||
|
)}
|
||||||
|
|
||||||
<CreateTeamAccountDialog
|
<CreateTeamAccountDialog
|
||||||
isOpen={isAddingAccount}
|
isOpen={isAddingAccount}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { cache } from 'react';
|
|||||||
|
|
||||||
import { createAccountsApi } from '@kit/accounts/api';
|
import { createAccountsApi } from '@kit/accounts/api';
|
||||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||||
|
import { createAccountCreationPolicyEvaluator } from '@kit/team-accounts/policies';
|
||||||
|
|
||||||
import featureFlagsConfig from '~/config/feature-flags.config';
|
import featureFlagsConfig from '~/config/feature-flags.config';
|
||||||
import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component';
|
import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component';
|
||||||
@@ -34,9 +35,41 @@ async function workspaceLoader() {
|
|||||||
requireUserInServerComponent(),
|
requireUserInServerComponent(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Check if user can create team accounts (policy check)
|
||||||
|
const canCreateTeamAccount = shouldLoadAccounts
|
||||||
|
? await checkCanCreateTeamAccount(user.id)
|
||||||
|
: { allowed: false, reason: undefined };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
workspace,
|
workspace,
|
||||||
user,
|
user,
|
||||||
|
canCreateTeamAccount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user can create a team account based on policies.
|
||||||
|
* Preliminary checks run without account name - name validation happens during submission.
|
||||||
|
*/
|
||||||
|
async function checkCanCreateTeamAccount(userId: string) {
|
||||||
|
const evaluator = createAccountCreationPolicyEvaluator();
|
||||||
|
const hasPolicies = await evaluator.hasPoliciesForStage('preliminary');
|
||||||
|
|
||||||
|
if (!hasPolicies) {
|
||||||
|
return { allowed: true, reason: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
userId,
|
||||||
|
accountName: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await evaluator.canCreateAccount(context, 'preliminary');
|
||||||
|
|
||||||
|
return {
|
||||||
|
allowed: result.allowed,
|
||||||
|
reason: result.reasons[0],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "next-supabase-saas-kit-turbo",
|
"name": "next-supabase-saas-kit-turbo",
|
||||||
"version": "2.21.20",
|
"version": "2.22.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"@radix-ui/react-icons": "^1.3.2",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@supabase/supabase-js": "catalog:",
|
"@supabase/supabase-js": "catalog:",
|
||||||
"@tanstack/react-query": "catalog:",
|
"@tanstack/react-query": "catalog:",
|
||||||
|
"@types/node": "catalog:",
|
||||||
"@types/react": "catalog:",
|
"@types/react": "catalog:",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
"./hooks/*": "./src/hooks/*.ts",
|
"./hooks/*": "./src/hooks/*.ts",
|
||||||
"./webhooks": "./src/server/services/webhooks/index.ts",
|
"./webhooks": "./src/server/services/webhooks/index.ts",
|
||||||
"./policies": "./src/server/policies/index.ts",
|
"./policies": "./src/server/policies/index.ts",
|
||||||
"./policies/orchestrator": "./src/server/policies/orchestrator.ts",
|
|
||||||
"./services/account-invitations.service": "./src/server/services/account-invitations.service.ts"
|
"./services/account-invitations.service": "./src/server/services/account-invitations.service.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function CreateTeamAccountDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CreateOrganizationAccountForm(props: { onClose: () => void }) {
|
function CreateOrganizationAccountForm(props: { onClose: () => void }) {
|
||||||
const [error, setError] = useState<boolean>();
|
const [error, setError] = useState<{ message?: string } | undefined>();
|
||||||
const [pending, startTransition] = useTransition();
|
const [pending, startTransition] = useTransition();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
@@ -78,14 +78,14 @@ function CreateOrganizationAccountForm(props: { onClose: () => void }) {
|
|||||||
onSubmit={form.handleSubmit((data) => {
|
onSubmit={form.handleSubmit((data) => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
try {
|
try {
|
||||||
const { error } = await createTeamAccountAction(data);
|
const result = await createTeamAccountAction(data);
|
||||||
|
|
||||||
if (error) {
|
if (result.error) {
|
||||||
setError(true);
|
setError({ message: result.message });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
if (!isRedirectError(error)) {
|
if (!isRedirectError(e)) {
|
||||||
setError(true);
|
setError({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -93,7 +93,7 @@ function CreateOrganizationAccountForm(props: { onClose: () => void }) {
|
|||||||
>
|
>
|
||||||
<div className={'flex flex-col space-y-4'}>
|
<div className={'flex flex-col space-y-4'}>
|
||||||
<If condition={error}>
|
<If condition={error}>
|
||||||
<CreateOrganizationErrorAlert />
|
<CreateOrganizationErrorAlert message={error?.message} />
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
@@ -150,7 +150,7 @@ function CreateOrganizationAccountForm(props: { onClose: () => void }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CreateOrganizationErrorAlert() {
|
function CreateOrganizationErrorAlert(props: { message?: string }) {
|
||||||
return (
|
return (
|
||||||
<Alert variant={'destructive'}>
|
<Alert variant={'destructive'}>
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
@@ -158,7 +158,11 @@ function CreateOrganizationErrorAlert() {
|
|||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
|
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<Trans i18nKey={'teams:createTeamErrorMessage'} />
|
{props.message ? (
|
||||||
|
<Trans i18nKey={props.message} defaults={props.message} />
|
||||||
|
) : (
|
||||||
|
<Trans i18nKey={'teams:createTeamErrorMessage'} />
|
||||||
|
)}
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { getLogger } from '@kit/shared/logger';
|
|||||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||||
|
|
||||||
import { CreateTeamSchema } from '../../schema/create-team.schema';
|
import { CreateTeamSchema } from '../../schema/create-team.schema';
|
||||||
|
import { createAccountCreationPolicyEvaluator } from '../policies';
|
||||||
import { createCreateTeamAccountService } from '../services/create-team-account.service';
|
import { createCreateTeamAccountService } from '../services/create-team-account.service';
|
||||||
|
|
||||||
export const createTeamAccountAction = enhanceAction(
|
export const createTeamAccountAction = enhanceAction(
|
||||||
@@ -23,19 +24,40 @@ export const createTeamAccountAction = enhanceAction(
|
|||||||
|
|
||||||
logger.info(ctx, `Creating team account...`);
|
logger.info(ctx, `Creating team account...`);
|
||||||
|
|
||||||
const { data, error } = await service.createNewOrganizationAccount({
|
// Check policies before creating
|
||||||
|
const evaluator = createAccountCreationPolicyEvaluator();
|
||||||
|
|
||||||
|
if (await evaluator.hasPoliciesForStage('submission')) {
|
||||||
|
const policyContext = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
userId: user.id,
|
||||||
|
accountName: name,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await evaluator.canCreateAccount(
|
||||||
|
policyContext,
|
||||||
|
'submission',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result.allowed) {
|
||||||
|
logger.warn(
|
||||||
|
{ ...ctx, reasons: result.reasons },
|
||||||
|
`Policy denied team account creation`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: result.reasons[0] ?? 'Policy denied account creation',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service throws on error, so no need to check for error
|
||||||
|
const { data } = await service.createNewOrganizationAccount({
|
||||||
name,
|
name,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
|
||||||
logger.error({ ...ctx, error }, `Failed to create team account`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
error: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(ctx, `Team account created`);
|
logger.info(ctx, `Team account created`);
|
||||||
|
|
||||||
const accountHomePath = '/home/' + data.slug;
|
const accountHomePath = '/home/' + data.slug;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import 'server-only';
|
||||||
|
|
||||||
|
import { createPolicyRegistry } from '@kit/policies';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature-specific registry for create account policies.
|
||||||
|
*/
|
||||||
|
export const createAccountPolicyRegistry = createPolicyRegistry();
|
||||||
|
|
||||||
|
// Register policies here
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import 'server-only';
|
||||||
|
|
||||||
|
import type { EvaluationResult } from '@kit/policies';
|
||||||
|
import { createPoliciesEvaluator } from '@kit/policies';
|
||||||
|
|
||||||
|
import { createAccountPolicyRegistry } from './create-account-policies';
|
||||||
|
import type { FeaturePolicyCreateAccountContext } from './feature-policy-create-account-context';
|
||||||
|
|
||||||
|
export interface CreateAccountPolicyEvaluator {
|
||||||
|
hasPoliciesForStage(stage: 'preliminary' | 'submission'): Promise<boolean>;
|
||||||
|
canCreateAccount(
|
||||||
|
context: FeaturePolicyCreateAccountContext,
|
||||||
|
stage: 'preliminary' | 'submission',
|
||||||
|
): Promise<EvaluationResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a create account policy evaluator
|
||||||
|
*/
|
||||||
|
export function createAccountCreationPolicyEvaluator(): CreateAccountPolicyEvaluator {
|
||||||
|
const evaluator =
|
||||||
|
createPoliciesEvaluator<FeaturePolicyCreateAccountContext>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Checks if there are any create account policies for the given stage
|
||||||
|
* @param stage - The stage to check if there are any policies for
|
||||||
|
*/
|
||||||
|
async hasPoliciesForStage(stage: 'preliminary' | 'submission') {
|
||||||
|
return evaluator.hasPoliciesForStage(createAccountPolicyRegistry, stage);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates the create account policies for the given context and stage
|
||||||
|
* @param context - The context for the create account policy
|
||||||
|
* @param stage - The stage to evaluate the policies for
|
||||||
|
*/
|
||||||
|
async canCreateAccount(
|
||||||
|
context: FeaturePolicyCreateAccountContext,
|
||||||
|
stage: 'preliminary' | 'submission',
|
||||||
|
) {
|
||||||
|
return evaluator.evaluate(
|
||||||
|
createAccountPolicyRegistry,
|
||||||
|
context,
|
||||||
|
'ALL',
|
||||||
|
stage,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { PolicyContext } from '@kit/policies';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal context for create account policies.
|
||||||
|
* Policies can fetch additional data internally using getSupabaseServerClient().
|
||||||
|
*/
|
||||||
|
export interface FeaturePolicyCreateAccountContext extends PolicyContext {
|
||||||
|
/** The ID of the user creating the account */
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
/** The name of the account being created (empty string for preliminary checks) */
|
||||||
|
accountName: string;
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
|
// Invitation policies
|
||||||
export { createInvitationsPolicyEvaluator } from './invitation-policies';
|
export { createInvitationsPolicyEvaluator } from './invitation-policies';
|
||||||
|
|
||||||
// Context building
|
|
||||||
export { createInvitationContextBuilder } from './invitation-context-builder';
|
export { createInvitationContextBuilder } from './invitation-context-builder';
|
||||||
|
|
||||||
// Type exports
|
|
||||||
export type { FeaturePolicyInvitationContext } from './feature-policy-invitation-context';
|
export type { FeaturePolicyInvitationContext } from './feature-policy-invitation-context';
|
||||||
|
|
||||||
|
// Create account policies
|
||||||
|
export { createAccountCreationPolicyEvaluator } from './create-account-policy-evaluator';
|
||||||
|
export type { CreateAccountPolicyEvaluator } from './create-account-policy-evaluator';
|
||||||
|
export { createAccountPolicyRegistry } from './create-account-policies';
|
||||||
|
export type { FeaturePolicyCreateAccountContext } from './feature-policy-create-account-context';
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"@kit/supabase": "workspace:*",
|
"@kit/supabase": "workspace:*",
|
||||||
"@kit/tsconfig": "workspace:*",
|
"@kit/tsconfig": "workspace:*",
|
||||||
"@supabase/supabase-js": "catalog:",
|
"@supabase/supabase-js": "catalog:",
|
||||||
|
"@types/node": "catalog:",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
|
|||||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -906,6 +906,9 @@ importers:
|
|||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 5.90.12(react@19.2.3)
|
version: 5.90.12(react@19.2.3)
|
||||||
|
'@types/node':
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 25.0.3
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 19.2.7
|
version: 19.2.7
|
||||||
@@ -1313,6 +1316,9 @@ importers:
|
|||||||
'@supabase/supabase-js':
|
'@supabase/supabase-js':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 2.89.0
|
version: 2.89.0
|
||||||
|
'@types/node':
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 25.0.3
|
||||||
next:
|
next:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 16.1.1(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
version: 16.1.1(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
@@ -14442,7 +14448,7 @@ snapshots:
|
|||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1))
|
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1))
|
||||||
eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1))
|
eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1))
|
||||||
eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1))
|
eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1))
|
||||||
@@ -14481,7 +14487,7 @@ snapshots:
|
|||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
unrs-resolver: 1.11.1
|
unrs-resolver: 1.11.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -14496,7 +14502,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)):
|
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rtsao/scc': 1.1.0
|
'@rtsao/scc': 1.1.0
|
||||||
array-includes: 3.1.9
|
array-includes: 3.1.9
|
||||||
|
|||||||
Reference in New Issue
Block a user