mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-12 14:01:18 +02:00
improvements to create user
This commit is contained in:
+3
-3
@@ -676,11 +676,11 @@
|
|||||||
"accessUserCreateDescription": "Follow the steps below to create a new user",
|
"accessUserCreateDescription": "Follow the steps below to create a new user",
|
||||||
"userSeeAll": "See All Users",
|
"userSeeAll": "See All Users",
|
||||||
"userTypeTitle": "User Type",
|
"userTypeTitle": "User Type",
|
||||||
"userTypeDescription": "Determine how you want to create the user",
|
"userTypeDescription": "Select the identity provider to use for this user",
|
||||||
"userSettings": "User Information",
|
"userSettings": "User Information",
|
||||||
"userSettingsDescription": "Enter the details for the new user",
|
"userSettingsDescription": "Enter the general details for the new user",
|
||||||
"inviteEmailSent": "Send invite email to user",
|
"inviteEmailSent": "Send invite email to user",
|
||||||
"inviteValid": "Invite Valid For (days)",
|
"inviteValid": "Invite Valid For",
|
||||||
"selectDuration": "Select duration",
|
"selectDuration": "Select duration",
|
||||||
"selectResource": "Select Resource",
|
"selectResource": "Select Resource",
|
||||||
"filterByResource": "Filter By Resource",
|
"filterByResource": "Filter By Resource",
|
||||||
|
|||||||
@@ -1,24 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||||
import {
|
import {
|
||||||
SettingsContainer,
|
Credenza,
|
||||||
SettingsSection,
|
CredenzaBody,
|
||||||
SettingsSectionBody,
|
CredenzaContent,
|
||||||
SettingsSectionDescription,
|
CredenzaDescription,
|
||||||
SettingsSectionForm,
|
CredenzaFooter,
|
||||||
SettingsSectionHeader,
|
CredenzaHeader,
|
||||||
SettingsSectionTitle
|
CredenzaTitle
|
||||||
} from "@app/components/Settings";
|
} from "@app/components/Credenza";
|
||||||
import { StrategyOption, StrategySelect } from "@app/components/StrategySelect";
|
import { StrategySelect } from "@app/components/StrategySelect";
|
||||||
import HeaderTitle from "@app/components/SettingsSectionTitle";
|
import HeaderTitle from "@app/components/SettingsSectionTitle";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import {
|
import { useActionState, useRef, useState, startTransition } from "react";
|
||||||
useActionState,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
startTransition
|
|
||||||
} from "react";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -42,7 +38,6 @@ import { AxiosResponse } from "axios";
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import CopyTextBox from "@app/components/CopyTextBox";
|
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { ListRolesResponse } from "@server/routers/role";
|
import { ListRolesResponse } from "@server/routers/role";
|
||||||
import { formatAxiosError } from "@app/lib/api";
|
import { formatAxiosError } from "@app/lib/api";
|
||||||
@@ -55,7 +50,15 @@ import IdpTypeIcon from "@app/components/IdpTypeIcon";
|
|||||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import OrgRolesTagField from "@app/components/OrgRolesTagField";
|
import OrgRolesTagField from "@app/components/OrgRolesTagField";
|
||||||
import CopyToClipboard from "@app/components/CopyToClipboard";
|
import {
|
||||||
|
SettingsContainer,
|
||||||
|
SettingsSection,
|
||||||
|
SettingsSectionBody,
|
||||||
|
SettingsSectionDescription,
|
||||||
|
SettingsSectionForm,
|
||||||
|
SettingsSectionHeader,
|
||||||
|
SettingsSectionTitle
|
||||||
|
} from "@app/components/Settings";
|
||||||
|
|
||||||
type UserType = "internal" | "oidc";
|
type UserType = "internal" | "oidc";
|
||||||
|
|
||||||
@@ -96,6 +99,7 @@ export default function Page() {
|
|||||||
"internal"
|
"internal"
|
||||||
);
|
);
|
||||||
const [inviteLink, setInviteLink] = useState<string | null>(null);
|
const [inviteLink, setInviteLink] = useState<string | null>(null);
|
||||||
|
const [isInviteDialogOpen, setIsInviteDialogOpen] = useState(false);
|
||||||
|
|
||||||
const [expiresInDays, setExpiresInDays] = useState(1);
|
const [expiresInDays, setExpiresInDays] = useState(1);
|
||||||
const [roles, setRoles] = useState<{ roleId: number; name: string }[]>([]);
|
const [roles, setRoles] = useState<{ roleId: number; name: string }[]>([]);
|
||||||
@@ -246,9 +250,9 @@ export default function Page() {
|
|||||||
build === "saas" || env.app.identityProviderMode === "org";
|
build === "saas" || env.app.identityProviderMode === "org";
|
||||||
|
|
||||||
const res = await api
|
const res = await api
|
||||||
.get<
|
.get<AxiosResponse<ListIdpsResponse>>(
|
||||||
AxiosResponse<ListIdpsResponse>
|
useOrgIdps ? `/org/${orgId}/idp` : "/idp"
|
||||||
>(useOrgIdps ? `/org/${orgId}/idp` : "/idp")
|
)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast({
|
toast({
|
||||||
@@ -350,13 +354,13 @@ export default function Page() {
|
|||||||
|
|
||||||
if (res && res.status === 200) {
|
if (res && res.status === 200) {
|
||||||
setInviteLink(res.data.data.inviteLink);
|
setInviteLink(res.data.data.inviteLink);
|
||||||
|
setExpiresInDays(parseInt(values.validForHours) / 24);
|
||||||
|
setIsInviteDialogOpen(true);
|
||||||
toast({
|
toast({
|
||||||
variant: "default",
|
variant: "default",
|
||||||
title: t("userInvited"),
|
title: t("userInvited"),
|
||||||
description: t("userInvitedDescription")
|
description: t("userInvitedDescription")
|
||||||
});
|
});
|
||||||
|
|
||||||
setExpiresInDays(parseInt(values.validForHours) / 24);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,6 +464,7 @@ export default function Page() {
|
|||||||
setSendEmail(env.email.emailEnabled);
|
setSendEmail(env.email.emailEnabled);
|
||||||
internalForm.reset();
|
internalForm.reset();
|
||||||
setInviteLink(null);
|
setInviteLink(null);
|
||||||
|
setIsInviteDialogOpen(false);
|
||||||
setExpiresInDays(1);
|
setExpiresInDays(1);
|
||||||
} else {
|
} else {
|
||||||
googleAzureForm.reset();
|
googleAzureForm.reset();
|
||||||
@@ -486,7 +491,7 @@ export default function Page() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
{!inviteLink && userOptions.length > 1 ? (
|
{userOptions.length > 1 ? (
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
@@ -508,8 +513,6 @@ export default function Page() {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{selectedOption === "internal" && dataLoaded && (
|
{selectedOption === "internal" && dataLoaded && (
|
||||||
<>
|
|
||||||
{!inviteLink ? (
|
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
@@ -533,9 +536,7 @@ export default function Page() {
|
|||||||
id="create-user-form"
|
id="create-user-form"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={
|
control={internalForm.control}
|
||||||
internalForm.control
|
|
||||||
}
|
|
||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
@@ -543,26 +544,40 @@ export default function Page() {
|
|||||||
{t("email")}
|
{t("email")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input {...field} />
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
{env.email.emailEnabled && (
|
||||||
control={
|
<div className="flex items-center space-x-2">
|
||||||
internalForm.control
|
<Checkbox
|
||||||
|
id="send-email"
|
||||||
|
checked={sendEmail}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
setSendEmail(
|
||||||
|
e as boolean
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="send-email"
|
||||||
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
{t("inviteEmailSent")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={internalForm.control}
|
||||||
name="validForHours"
|
name="validForHours"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t(
|
{t("inviteValid")}
|
||||||
"inviteValid"
|
|
||||||
)}
|
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
onValueChange={
|
onValueChange={
|
||||||
@@ -619,66 +634,11 @@ export default function Page() {
|
|||||||
invitePaywallMessage
|
invitePaywallMessage
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{env.email.emailEnabled && (
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="send-email"
|
|
||||||
checked={
|
|
||||||
sendEmail
|
|
||||||
}
|
|
||||||
onCheckedChange={(
|
|
||||||
e
|
|
||||||
) =>
|
|
||||||
setSendEmail(
|
|
||||||
e as boolean
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="send-email"
|
|
||||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"inviteEmailSent"
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
) : (
|
|
||||||
<SettingsSection>
|
|
||||||
<SettingsSectionHeader>
|
|
||||||
<SettingsSectionTitle>
|
|
||||||
{t("userInvited")}
|
|
||||||
</SettingsSectionTitle>
|
|
||||||
<SettingsSectionDescription>
|
|
||||||
{sendEmail
|
|
||||||
? t(
|
|
||||||
"inviteEmailSentDescription"
|
|
||||||
)
|
|
||||||
: t("inviteSentDescription")}
|
|
||||||
</SettingsSectionDescription>
|
|
||||||
</SettingsSectionHeader>
|
|
||||||
<SettingsSectionBody>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<p>
|
|
||||||
{t("inviteExpiresIn", {
|
|
||||||
days: expiresInDays
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
<CopyToClipboard
|
|
||||||
text={inviteLink}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</SettingsSectionBody>
|
|
||||||
</SettingsSection>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedOption &&
|
{selectedOption &&
|
||||||
@@ -898,24 +858,61 @@ export default function Page() {
|
|||||||
<div className="flex justify-end space-x-2 mt-8">
|
<div className="flex justify-end space-x-2 mt-8">
|
||||||
{selectedOption && dataLoaded && (
|
{selectedOption && dataLoaded && (
|
||||||
<Button
|
<Button
|
||||||
type={inviteLink ? "button" : "submit"}
|
type="submit"
|
||||||
form={inviteLink ? undefined : "create-user-form"}
|
form="create-user-form"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onClick={
|
|
||||||
inviteLink
|
|
||||||
? () =>
|
|
||||||
router.push(
|
|
||||||
`/${orgId}/settings/access/users`
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{inviteLink ? t("done") : t("accessUserCreate")}
|
{t("accessUserCreate")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Credenza
|
||||||
|
open={isInviteDialogOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setIsInviteDialogOpen(open);
|
||||||
|
if (!open) {
|
||||||
|
setInviteLink(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CredenzaContent>
|
||||||
|
<CredenzaHeader>
|
||||||
|
<CredenzaTitle>{t("userInvited")}</CredenzaTitle>
|
||||||
|
<CredenzaDescription>
|
||||||
|
{sendEmail
|
||||||
|
? t("inviteEmailSentDescription")
|
||||||
|
: t("inviteSentDescription")}
|
||||||
|
</CredenzaDescription>
|
||||||
|
</CredenzaHeader>
|
||||||
|
<CredenzaBody>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>
|
||||||
|
{t("inviteExpiresIn", {
|
||||||
|
days: expiresInDays
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{inviteLink && (
|
||||||
|
<CopyToClipboard
|
||||||
|
text={inviteLink}
|
||||||
|
isLink={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CredenzaBody>
|
||||||
|
<CredenzaFooter>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/${orgId}/settings/access/users`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("done")}
|
||||||
|
</Button>
|
||||||
|
</CredenzaFooter>
|
||||||
|
</CredenzaContent>
|
||||||
|
</Credenza>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,7 +171,20 @@ export default function UsersTable({
|
|||||||
<Icon className="ml-2 h-4 w-4" />
|
<Icon className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="inline-flex gap-1 items-center">
|
||||||
|
{row.original.displayUsername}{" "}
|
||||||
|
{row.original.id === user.userId && (
|
||||||
|
<>
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
·
|
||||||
|
</span>{" "}
|
||||||
|
<span className="text-primary">you</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "idpName",
|
accessorKey: "idpName",
|
||||||
|
|||||||
Reference in New Issue
Block a user