Merge branch 'canary' into typo-fix

This commit is contained in:
Mauricio Siu
2026-04-03 15:26:54 -06:00
36 changed files with 17645 additions and 154 deletions
@@ -26,6 +26,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Switch } from "@/components/ui/switch";
import { EnterpriseFeatureLocked } from "@/components/proprietary/enterprise-feature-gate";
import { api, type RouterOutputs } from "@/utils/api";
/** Shape returned by project.allForPermissions (admin only). Used for the permissions UI. */
@@ -170,6 +171,7 @@ const addPermissions = z.object({
accessedProjects: z.array(z.string()).optional(),
accessedEnvironments: z.array(z.string()).optional(),
accessedServices: z.array(z.string()).optional(),
accessedGitProviders: z.array(z.string()).optional(),
canCreateProjects: z.boolean().optional().default(false),
canCreateServices: z.boolean().optional().default(false),
canDeleteProjects: z.boolean().optional().default(false),
@@ -196,6 +198,15 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
const { data: projects } = api.project.allForPermissions.useQuery(undefined, {
enabled: isOpen,
});
const { data: haveValidLicense } =
api.licenseKey.haveValidLicenseKey.useQuery();
const { data: gitProviders } = api.gitProvider.allForPermissions.useQuery(
undefined,
{
enabled: isOpen && !!haveValidLicense,
},
);
const { data, refetch } = api.user.one.useQuery(
{
@@ -214,6 +225,7 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
accessedProjects: [],
accessedEnvironments: [],
accessedServices: [],
accessedGitProviders: [],
canDeleteEnvironments: false,
canCreateProjects: false,
canCreateServices: false,
@@ -235,6 +247,7 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
accessedProjects: data.accessedProjects || [],
accessedEnvironments: data.accessedEnvironments || [],
accessedServices: data.accessedServices || [],
accessedGitProviders: data.accessedGitProviders || [],
canCreateProjects: data.canCreateProjects,
canCreateServices: data.canCreateServices,
canDeleteProjects: data.canDeleteProjects,
@@ -262,6 +275,7 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
accessedProjects: data.accessedProjects || [],
accessedEnvironments: data.accessedEnvironments || [],
accessedServices: data.accessedServices || [],
accessedGitProviders: data.accessedGitProviders || [],
canAccessToDocker: data.canAccessToDocker,
canAccessToAPI: data.canAccessToAPI,
canAccessToSSHKeys: data.canAccessToSSHKeys,
@@ -870,6 +884,78 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
</FormItem>
)}
/>
{haveValidLicense ? (
<FormField
control={form.control}
name="accessedGitProviders"
render={() => (
<FormItem className="md:col-span-2">
<div className="mb-4">
<FormLabel className="text-base">Git Providers</FormLabel>
<FormDescription>
Select the Git Providers that the user can access
</FormDescription>
</div>
{gitProviders?.length === 0 && (
<p className="text-sm text-muted-foreground">
No git providers found
</p>
)}
<div className="grid md:grid-cols-1 gap-2">
{gitProviders?.map((provider) => (
<FormField
key={provider.gitProviderId}
control={form.control}
name="accessedGitProviders"
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0 rounded-lg border p-3">
<FormControl>
<Checkbox
checked={field.value?.includes(
provider.gitProviderId,
)}
onCheckedChange={(checked) => {
if (checked) {
field.onChange([
...(field.value || []),
provider.gitProviderId,
]);
} else {
field.onChange(
field.value?.filter(
(v) => v !== provider.gitProviderId,
),
);
}
}}
/>
</FormControl>
<div className="flex items-center gap-2">
<FormLabel className="text-sm cursor-pointer">
{provider.name}
</FormLabel>
<span className="text-xs text-muted-foreground capitalize">
({provider.providerType})
</span>
</div>
</FormItem>
)}
/>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
) : (
<div className="md:col-span-2">
<EnterpriseFeatureLocked
compact
title="Git Provider Assignment"
description="Assign specific Git Providers to users with an Enterprise license."
/>
</div>
)}
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2">
<Button
isLoading={isPending}