mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-03 08:39:09 +00:00
fix issues from test deploy
This commit is contained in:
@@ -126,7 +126,7 @@ export default function CreateRoleForm({
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8:w"
|
||||
className="space-y-8"
|
||||
id="create-role-form"
|
||||
>
|
||||
<FormField
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuTrigger
|
||||
} from "@app/components/ui/dropdown-menu";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { ArrowUpDown, Crown, MoreHorizontal } from "lucide-react";
|
||||
@@ -54,11 +54,11 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
header: "Description"
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
@@ -90,16 +90,15 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>
|
||||
<button
|
||||
className="text-red-500"
|
||||
onClick={() => {
|
||||
setIsDeleteModalOpen(true);
|
||||
setUserToRemove(roleRow);
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setIsDeleteModalOpen(true);
|
||||
setUserToRemove(roleRow);
|
||||
}}
|
||||
>
|
||||
<span className="text-red-500">
|
||||
Delete Role
|
||||
</button>
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -107,8 +106,8 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -128,9 +127,7 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
|
||||
roleToDelete={roleToRemove}
|
||||
afterDelete={() => {
|
||||
setRoles((prev) =>
|
||||
prev.filter(
|
||||
(r) => r.roleId !== roleToRemove.roleId,
|
||||
),
|
||||
prev.filter((r) => r.roleId !== roleToRemove.roleId)
|
||||
);
|
||||
setUserToRemove(null);
|
||||
}}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
FormMessage
|
||||
} from "@app/components/ui/form";
|
||||
import { Input } from "@app/components/ui/input";
|
||||
import {
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SelectValue
|
||||
} from "@app/components/ui/select";
|
||||
import { useToast } from "@app/hooks/useToast";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@@ -33,13 +33,14 @@ import {
|
||||
CredenzaDescription,
|
||||
CredenzaFooter,
|
||||
CredenzaHeader,
|
||||
CredenzaTitle,
|
||||
CredenzaTitle
|
||||
} from "@app/components/Credenza";
|
||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||
import { ListRolesResponse } from "@server/routers/role";
|
||||
import { formatAxiosError } from "@app/lib/utils";
|
||||
import { createApiClient } from "@app/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { Checkbox } from "@app/components/ui/checkbox";
|
||||
|
||||
type InviteUserFormProps = {
|
||||
open: boolean;
|
||||
@@ -49,14 +50,16 @@ type InviteUserFormProps = {
|
||||
const formSchema = z.object({
|
||||
email: z.string().email({ message: "Invalid email address" }),
|
||||
validForHours: z.string().min(1, { message: "Please select a duration" }),
|
||||
roleId: z.string().min(1, { message: "Please select a role" }),
|
||||
roleId: z.string().min(1, { message: "Please select a role" })
|
||||
});
|
||||
|
||||
export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
const { toast } = useToast();
|
||||
const { org } = useOrgContext();
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
const { env } = useEnvContext();
|
||||
|
||||
const api = createApiClient({ env });
|
||||
|
||||
const [inviteLink, setInviteLink] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -64,6 +67,8 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
|
||||
const [roles, setRoles] = useState<{ roleId: number; name: string }[]>([]);
|
||||
|
||||
const [sendEmail, setSendEmail] = useState(env.EMAIL_ENABLED === "true");
|
||||
|
||||
const validFor = [
|
||||
{ hours: 24, name: "1 day" },
|
||||
{ hours: 48, name: "2 days" },
|
||||
@@ -71,7 +76,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
{ hours: 96, name: "4 days" },
|
||||
{ hours: 120, name: "5 days" },
|
||||
{ hours: 144, name: "6 days" },
|
||||
{ hours: 168, name: "7 days" },
|
||||
{ hours: 168, name: "7 days" }
|
||||
];
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
@@ -79,8 +84,8 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
defaultValues: {
|
||||
email: "",
|
||||
validForHours: "72",
|
||||
roleId: "",
|
||||
},
|
||||
roleId: ""
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -90,9 +95,9 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
|
||||
async function fetchRoles() {
|
||||
const res = await api
|
||||
.get<AxiosResponse<ListRolesResponse>>(
|
||||
`/org/${org?.org.orgId}/roles`
|
||||
)
|
||||
.get<
|
||||
AxiosResponse<ListRolesResponse>
|
||||
>(`/org/${org?.org.orgId}/roles`)
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast({
|
||||
@@ -101,7 +106,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred while fetching the roles"
|
||||
),
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,6 +132,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
email: values.email,
|
||||
roleId: parseInt(values.roleId),
|
||||
validHours: parseInt(values.validForHours),
|
||||
sendEmail: sendEmail
|
||||
} as InviteUserBody
|
||||
)
|
||||
.catch((e) => {
|
||||
@@ -136,7 +142,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred while inviting the user"
|
||||
),
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -145,7 +151,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
toast({
|
||||
variant: "default",
|
||||
title: "User invited",
|
||||
description: "The user has been successfully invited.",
|
||||
description: "The user has been successfully invited."
|
||||
});
|
||||
|
||||
setExpiresInDays(parseInt(values.validForHours) / 24);
|
||||
@@ -198,6 +204,27 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{env.EMAIL_ENABLED === "true" && (
|
||||
<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"
|
||||
>
|
||||
Send invite email to user
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="roleId"
|
||||
@@ -281,11 +308,21 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||
|
||||
{inviteLink && (
|
||||
<div className="max-w-md space-y-4">
|
||||
<p>
|
||||
The user has been successfully invited.
|
||||
They must access the link below to
|
||||
accept the invitation.
|
||||
</p>
|
||||
{sendEmail && (
|
||||
<p>
|
||||
An email has been sent to the user
|
||||
with the access link below. They
|
||||
must access the link to accept the
|
||||
invitation.
|
||||
</p>
|
||||
)}
|
||||
{!sendEmail && (
|
||||
<p>
|
||||
The user has been invited. They must
|
||||
access the link below to accept the
|
||||
invitation.
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
The invite will expire in{" "}
|
||||
<b>
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuTrigger
|
||||
} from "@app/components/ui/dropdown-menu";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { ArrowRight, ArrowUpDown, Crown, MoreHorizontal } from "lucide-react";
|
||||
@@ -43,7 +43,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
const user = useUserContext();
|
||||
const { org } = useOrgContext();
|
||||
@@ -64,7 +64,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
@@ -80,7 +80,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "role",
|
||||
@@ -108,7 +108,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||
<span>{userRow.role}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
@@ -149,20 +149,19 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
{userRow.email !== user?.email && (
|
||||
<DropdownMenuItem>
|
||||
<button
|
||||
className="text-red-500"
|
||||
onClick={() => {
|
||||
setIsDeleteModalOpen(
|
||||
true,
|
||||
);
|
||||
setSelectedUser(
|
||||
userRow,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setIsDeleteModalOpen(
|
||||
true
|
||||
);
|
||||
setSelectedUser(
|
||||
userRow
|
||||
);
|
||||
}}
|
||||
>
|
||||
<span className="text-red-500">
|
||||
Remove User
|
||||
</button>
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
@@ -183,8 +182,8 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
async function removeUser() {
|
||||
@@ -197,8 +196,8 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||
title: "Failed to remove user",
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred while removing the user.",
|
||||
),
|
||||
"An error occurred while removing the user."
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -206,11 +205,11 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||
toast({
|
||||
variant: "default",
|
||||
title: "User removed",
|
||||
description: `The user ${selectedUser.email} has been removed from the organization.`,
|
||||
description: `The user ${selectedUser.email} has been removed from the organization.`
|
||||
});
|
||||
|
||||
setUsers((prev) =>
|
||||
prev.filter((u) => u.id !== selectedUser?.id),
|
||||
prev.filter((u) => u.id !== selectedUser?.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,63 +637,72 @@ export default function ResourceAuthenticationPage() {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Form {...whitelistForm}>
|
||||
<form className="space-y-8">
|
||||
<FormField
|
||||
control={whitelistForm.control}
|
||||
name="emails"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col items-start">
|
||||
<FormLabel>
|
||||
Whitelisted Emails
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<TagInput
|
||||
{...field}
|
||||
activeTagIndex={
|
||||
activeEmailTagIndex
|
||||
}
|
||||
validateTag={(tag) => {
|
||||
return z
|
||||
.string()
|
||||
.email()
|
||||
.safeParse(tag)
|
||||
.success;
|
||||
}}
|
||||
setActiveTagIndex={
|
||||
setActiveEmailTagIndex
|
||||
}
|
||||
placeholder="Enter an email"
|
||||
tags={
|
||||
whitelistForm.getValues()
|
||||
.emails
|
||||
}
|
||||
setTags={(newRoles) => {
|
||||
whitelistForm.setValue(
|
||||
"emails",
|
||||
newRoles as [
|
||||
Tag,
|
||||
...Tag[]
|
||||
]
|
||||
);
|
||||
}}
|
||||
allowDuplicates={false}
|
||||
sortTags={true}
|
||||
styleClasses={{
|
||||
tag: {
|
||||
body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
|
||||
},
|
||||
input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
|
||||
inlineTagsContainer:
|
||||
"bg-transparent"
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
{whitelistEnabled && (
|
||||
<Form {...whitelistForm}>
|
||||
<form className="space-y-8">
|
||||
<FormField
|
||||
control={whitelistForm.control}
|
||||
name="emails"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col items-start">
|
||||
<FormLabel>
|
||||
Whitelisted Emails
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<TagInput
|
||||
{...field}
|
||||
activeTagIndex={
|
||||
activeEmailTagIndex
|
||||
}
|
||||
validateTag={(
|
||||
tag
|
||||
) => {
|
||||
return z
|
||||
.string()
|
||||
.email()
|
||||
.safeParse(
|
||||
tag
|
||||
).success;
|
||||
}}
|
||||
setActiveTagIndex={
|
||||
setActiveEmailTagIndex
|
||||
}
|
||||
placeholder="Enter an email"
|
||||
tags={
|
||||
whitelistForm.getValues()
|
||||
.emails
|
||||
}
|
||||
setTags={(
|
||||
newRoles
|
||||
) => {
|
||||
whitelistForm.setValue(
|
||||
"emails",
|
||||
newRoles as [
|
||||
Tag,
|
||||
...Tag[]
|
||||
]
|
||||
);
|
||||
}}
|
||||
allowDuplicates={
|
||||
false
|
||||
}
|
||||
sortTags={true}
|
||||
styleClasses={{
|
||||
tag: {
|
||||
body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
|
||||
},
|
||||
input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
|
||||
inlineTagsContainer:
|
||||
"bg-transparent"
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
|
||||
<Button
|
||||
loading={loadingSaveWhitelist}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuTrigger
|
||||
} from "@app/components/ui/dropdown-menu";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import {
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
Check,
|
||||
ArrowUpRight,
|
||||
ShieldOff,
|
||||
ShieldCheck,
|
||||
ShieldCheck
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -64,7 +64,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error deleting resource",
|
||||
description: formatAxiosError(e, "Error deleting resource"),
|
||||
description: formatAxiosError(e, "Error deleting resource")
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
@@ -88,7 +88,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "site",
|
||||
@@ -117,7 +117,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
<ArrowUpRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "domain",
|
||||
@@ -139,16 +139,16 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
resourceRow.domain,
|
||||
resourceRow.domain
|
||||
);
|
||||
const originalIcon = document.querySelector(
|
||||
`#icon-${resourceRow.id}`,
|
||||
`#icon-${resourceRow.id}`
|
||||
);
|
||||
if (originalIcon) {
|
||||
originalIcon.classList.add("hidden");
|
||||
}
|
||||
const checkIcon = document.querySelector(
|
||||
`#check-icon-${resourceRow.id}`,
|
||||
`#check-icon-${resourceRow.id}`
|
||||
);
|
||||
if (checkIcon) {
|
||||
checkIcon.classList.remove("hidden");
|
||||
@@ -156,7 +156,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
checkIcon.classList.add("hidden");
|
||||
if (originalIcon) {
|
||||
originalIcon.classList.remove(
|
||||
"hidden",
|
||||
"hidden"
|
||||
);
|
||||
}
|
||||
}, 2000);
|
||||
@@ -175,7 +175,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "hasAuth",
|
||||
@@ -209,7 +209,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
@@ -241,18 +241,15 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
View settings
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedResource(
|
||||
resourceRow,
|
||||
);
|
||||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
className="text-red-500"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setSelectedResource(resourceRow);
|
||||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="text-red-500">
|
||||
Delete
|
||||
</button>
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -267,8 +264,8 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -197,12 +197,16 @@ export default function CreateShareLinkForm({
|
||||
const link = constructShareLink(
|
||||
values.resourceId,
|
||||
token.accessTokenId,
|
||||
token.tokenHash
|
||||
token.accessToken
|
||||
);
|
||||
setLink(link);
|
||||
onCreated?.({
|
||||
...token,
|
||||
resourceName: values.resourceName
|
||||
accessTokenId: token.accessTokenId,
|
||||
resourceId: token.resourceId,
|
||||
resourceName: values.resourceName,
|
||||
title: token.title,
|
||||
createdAt: token.createdAt,
|
||||
expiresAt: token.expiresAt
|
||||
});
|
||||
}
|
||||
|
||||
@@ -285,7 +289,9 @@ export default function CreateShareLinkForm({
|
||||
r
|
||||
) => (
|
||||
<CommandItem
|
||||
value={r.name}
|
||||
value={
|
||||
r.name
|
||||
}
|
||||
key={
|
||||
r.resourceId
|
||||
}
|
||||
@@ -441,6 +447,10 @@ export default function CreateShareLinkForm({
|
||||
)}
|
||||
{link && (
|
||||
<div className="max-w-md space-y-4">
|
||||
<p>
|
||||
You will be able to see this link once.
|
||||
Make sure to copy it.
|
||||
</p>
|
||||
<p>
|
||||
Anyone with this link can access the
|
||||
resource. Share it with care.
|
||||
|
||||
@@ -34,9 +34,14 @@ import moment from "moment";
|
||||
import CreateShareLinkForm from "./CreateShareLinkForm";
|
||||
import { constructShareLink } from "@app/lib/shareLinks";
|
||||
|
||||
export type ShareLinkRow = ArrayElement<
|
||||
ListAccessTokensResponse["accessTokens"]
|
||||
>;
|
||||
export type ShareLinkRow = {
|
||||
accessTokenId: string;
|
||||
resourceId: number;
|
||||
resourceName: string;
|
||||
title: string | null;
|
||||
createdAt: number;
|
||||
expiresAt: number | null;
|
||||
};
|
||||
|
||||
type ShareLinksTableProps = {
|
||||
shareLinks: ShareLinkRow[];
|
||||
@@ -64,7 +69,10 @@ export default function ShareLinksTable({
|
||||
await api.delete(`/access-token/${id}`).catch((e) => {
|
||||
toast({
|
||||
title: "Failed to delete link",
|
||||
description: formatAxiosError(e, "An error occurred deleting link"),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred deleting link"
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,7 +81,7 @@ export default function ShareLinksTable({
|
||||
|
||||
toast({
|
||||
title: "Link deleted",
|
||||
description: "The link has been deleted",
|
||||
description: "The link has been deleted"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,69 +131,69 @@ export default function ShareLinksTable({
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "domain",
|
||||
header: "Link",
|
||||
cell: ({ row }) => {
|
||||
const r = row.original;
|
||||
|
||||
const link = constructShareLink(
|
||||
r.resourceId,
|
||||
r.accessTokenId,
|
||||
r.tokenHash
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Link
|
||||
href={link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:underline mr-2"
|
||||
>
|
||||
{formatLink(link)}
|
||||
</Link>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(link);
|
||||
const originalIcon = document.querySelector(
|
||||
`#icon-${r.accessTokenId}`
|
||||
);
|
||||
if (originalIcon) {
|
||||
originalIcon.classList.add("hidden");
|
||||
}
|
||||
const checkIcon = document.querySelector(
|
||||
`#check-icon-${r.accessTokenId}`
|
||||
);
|
||||
if (checkIcon) {
|
||||
checkIcon.classList.remove("hidden");
|
||||
setTimeout(() => {
|
||||
checkIcon.classList.add("hidden");
|
||||
if (originalIcon) {
|
||||
originalIcon.classList.remove(
|
||||
"hidden"
|
||||
);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Copy
|
||||
id={`icon-${r.accessTokenId}`}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<Check
|
||||
id={`check-icon-${r.accessTokenId}`}
|
||||
className="hidden text-green-500 h-4 w-4"
|
||||
/>
|
||||
<span className="sr-only">Copy link</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
// {
|
||||
// accessorKey: "domain",
|
||||
// header: "Link",
|
||||
// cell: ({ row }) => {
|
||||
// const r = row.original;
|
||||
//
|
||||
// const link = constructShareLink(
|
||||
// r.resourceId,
|
||||
// r.accessTokenId,
|
||||
// r.tokenHash
|
||||
// );
|
||||
//
|
||||
// return (
|
||||
// <div className="flex items-center">
|
||||
// <Link
|
||||
// href={link}
|
||||
// target="_blank"
|
||||
// rel="noopener noreferrer"
|
||||
// className="hover:underline mr-2"
|
||||
// >
|
||||
// {formatLink(link)}
|
||||
// </Link>
|
||||
// <Button
|
||||
// variant="ghost"
|
||||
// className="h-6 w-6 p-0"
|
||||
// onClick={() => {
|
||||
// navigator.clipboard.writeText(link);
|
||||
// const originalIcon = document.querySelector(
|
||||
// `#icon-${r.accessTokenId}`
|
||||
// );
|
||||
// if (originalIcon) {
|
||||
// originalIcon.classList.add("hidden");
|
||||
// }
|
||||
// const checkIcon = document.querySelector(
|
||||
// `#check-icon-${r.accessTokenId}`
|
||||
// );
|
||||
// if (checkIcon) {
|
||||
// checkIcon.classList.remove("hidden");
|
||||
// setTimeout(() => {
|
||||
// checkIcon.classList.add("hidden");
|
||||
// if (originalIcon) {
|
||||
// originalIcon.classList.remove(
|
||||
// "hidden"
|
||||
// );
|
||||
// }
|
||||
// }, 2000);
|
||||
// }
|
||||
// }}
|
||||
// >
|
||||
// <Copy
|
||||
// id={`icon-${r.accessTokenId}`}
|
||||
// className="h-4 w-4"
|
||||
// />
|
||||
// <Check
|
||||
// id={`check-icon-${r.accessTokenId}`}
|
||||
// className="hidden text-green-500 h-4 w-4"
|
||||
// />
|
||||
// <span className="sr-only">Copy link</span>
|
||||
// </Button>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: ({ column }) => {
|
||||
|
||||
@@ -46,9 +46,9 @@ export default async function ShareLinksPage(props: ShareLinksPageProps) {
|
||||
redirect(`/${params.orgId}/settings/resources`);
|
||||
}
|
||||
|
||||
const rows: ShareLinkRow[] = tokens.map((token) => {
|
||||
return token;
|
||||
});
|
||||
const rows: ShareLinkRow[] = tokens.map(
|
||||
(token) => ({ ...token }) as ShareLinkRow
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
FormMessage
|
||||
} from "@app/components/ui/form";
|
||||
import { Input } from "@app/components/ui/input";
|
||||
import { useToast } from "@app/hooks/useToast";
|
||||
@@ -24,11 +24,15 @@ import {
|
||||
CredenzaDescription,
|
||||
CredenzaFooter,
|
||||
CredenzaHeader,
|
||||
CredenzaTitle,
|
||||
CredenzaTitle
|
||||
} from "@app/components/Credenza";
|
||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { CreateSiteBody, PickSiteDefaultsResponse } from "@server/routers/site";
|
||||
import {
|
||||
CreateSiteBody,
|
||||
CreateSiteResponse,
|
||||
PickSiteDefaultsResponse
|
||||
} from "@server/routers/site";
|
||||
import { generateKeypair } from "../[niceId]/components/wireguardConfig";
|
||||
import CopyTextBox from "@app/components/CopyTextBox";
|
||||
import { Checkbox } from "@app/components/ui/checkbox";
|
||||
@@ -37,42 +41,49 @@ import {
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SelectValue
|
||||
} from "@app/components/ui/select";
|
||||
import { formatAxiosError } from "@app/lib/utils";
|
||||
import { createApiClient } from "@app/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { SiteRow } from "./SitesTable";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
const method = [
|
||||
{ label: "Newt", value: "newt" },
|
||||
{ label: "Wireguard", value: "wireguard" },
|
||||
{ label: "WireGuard", value: "wireguard" }
|
||||
] as const;
|
||||
|
||||
const accountFormSchema = z.object({
|
||||
const createSiteFormSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(2, {
|
||||
message: "Name must be at least 2 characters.",
|
||||
message: "Name must be at least 2 characters."
|
||||
})
|
||||
.max(30, {
|
||||
message: "Name must not be longer than 30 characters.",
|
||||
message: "Name must not be longer than 30 characters."
|
||||
}),
|
||||
method: z.enum(["wireguard", "newt"]),
|
||||
method: z.enum(["wireguard", "newt"])
|
||||
});
|
||||
|
||||
type AccountFormValues = z.infer<typeof accountFormSchema>;
|
||||
type CreateSiteFormValues = z.infer<typeof createSiteFormSchema>;
|
||||
|
||||
const defaultValues: Partial<AccountFormValues> = {
|
||||
const defaultValues: Partial<CreateSiteFormValues> = {
|
||||
name: "",
|
||||
method: "newt",
|
||||
method: "newt"
|
||||
};
|
||||
|
||||
type CreateSiteFormProps = {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
onCreate?: (site: SiteRow) => void;
|
||||
};
|
||||
|
||||
export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
|
||||
export default function CreateSiteForm({
|
||||
open,
|
||||
setOpen,
|
||||
onCreate
|
||||
}: CreateSiteFormProps) {
|
||||
const { toast } = useToast();
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
@@ -96,9 +107,9 @@ export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
|
||||
setIsChecked(checked);
|
||||
};
|
||||
|
||||
const form = useForm<AccountFormValues>({
|
||||
resolver: zodResolver(accountFormSchema),
|
||||
defaultValues,
|
||||
const form = useForm<CreateSiteFormValues>({
|
||||
resolver: zodResolver(createSiteFormSchema),
|
||||
defaultValues
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -114,7 +125,7 @@ export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error picking site defaults",
|
||||
description: formatAxiosError(e),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
@@ -125,7 +136,7 @@ export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
async function onSubmit(data: AccountFormValues) {
|
||||
async function onSubmit(data: CreateSiteFormValues) {
|
||||
setLoading(true);
|
||||
if (!siteDefaults || !keypair) {
|
||||
return;
|
||||
@@ -135,29 +146,44 @@ export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
|
||||
subnet: siteDefaults.subnet,
|
||||
exitNodeId: siteDefaults.exitNodeId,
|
||||
pubKey: keypair.publicKey,
|
||||
type: data.method,
|
||||
type: data.method
|
||||
};
|
||||
if (data.method === "newt") {
|
||||
payload.secret = siteDefaults.newtSecret;
|
||||
payload.newtId = siteDefaults.newtId;
|
||||
}
|
||||
const res = await api
|
||||
.put(`/org/${orgId}/site/`, payload)
|
||||
.put<
|
||||
AxiosResponse<CreateSiteResponse>
|
||||
>(`/org/${orgId}/site/`, payload)
|
||||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error creating site",
|
||||
description: formatAxiosError(e),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
});
|
||||
|
||||
if (res && res.status === 201) {
|
||||
const niceId = res.data.data.niceId;
|
||||
// navigate to the site page
|
||||
router.push(`/${orgId}/settings/sites/${niceId}`);
|
||||
// router.push(`/${orgId}/settings/sites/${niceId}`);
|
||||
|
||||
// close the modal
|
||||
setOpen(false);
|
||||
|
||||
const data = res.data.data;
|
||||
|
||||
onCreate?.({
|
||||
name: data.name,
|
||||
id: data.siteId,
|
||||
nice: data.niceId.toString(),
|
||||
mbIn: "0 MB",
|
||||
mbOut: "0 MB",
|
||||
orgId: orgId as string,
|
||||
type: data.type as any,
|
||||
online: false
|
||||
});
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
@@ -275,8 +301,8 @@ PersistentKeepalive = 5`
|
||||
{form.watch("method") === "wireguard" &&
|
||||
!isLoading ? (
|
||||
<CopyTextBox text={wgConfig} />
|
||||
) : form.watch("method") === "wireguard" &&
|
||||
isLoading ? (
|
||||
) : form.watch("method") ===
|
||||
"wireguard" && isLoading ? (
|
||||
<p>
|
||||
Loading WireGuard
|
||||
configuration...
|
||||
|
||||
@@ -6,10 +6,16 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuTrigger
|
||||
} from "@app/components/ui/dropdown-menu";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { ArrowRight, ArrowUpDown, Check, MoreHorizontal, X } from "lucide-react";
|
||||
import {
|
||||
ArrowRight,
|
||||
ArrowUpDown,
|
||||
Check,
|
||||
MoreHorizontal,
|
||||
X
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { AxiosResponse } from "axios";
|
||||
@@ -45,14 +51,10 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [selectedSite, setSelectedSite] = useState<SiteRow | null>(null);
|
||||
const [rows, setRows] = useState<SiteRow[]>(sites);
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
const callApi = async () => {
|
||||
const res = await api.put<AxiosResponse<any>>(`/newt`);
|
||||
console.log(res);
|
||||
};
|
||||
|
||||
const deleteSite = (siteId: number) => {
|
||||
api.delete(`/site/${siteId}`)
|
||||
.catch((e) => {
|
||||
@@ -60,7 +62,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error deleting site",
|
||||
description: formatAxiosError(e, "Error deleting site"),
|
||||
description: formatAxiosError(e, "Error deleting site")
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
@@ -84,7 +86,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "nice",
|
||||
@@ -100,7 +102,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "mbIn",
|
||||
@@ -116,7 +118,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "mbOut",
|
||||
@@ -132,7 +134,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
@@ -167,7 +169,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "online",
|
||||
@@ -187,23 +189,23 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
cell: ({ row }) => {
|
||||
const originalRow = row.original;
|
||||
console.log(originalRow.online);
|
||||
|
||||
|
||||
if (originalRow.online) {
|
||||
return (
|
||||
<span className="text-green-500 flex items-center space-x-2">
|
||||
<Check className="w-4 h-4" />
|
||||
<span>Online</span>
|
||||
<Check className="w-4 h-4" />
|
||||
<span>Online</span>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span className="text-red-500 flex items-center space-x-2">
|
||||
<X className="w-4 h-4" />
|
||||
<span>Offline</span>
|
||||
<X className="w-4 h-4" />
|
||||
<span>Offline</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
@@ -229,16 +231,13 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
View settings
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedSite(siteRow);
|
||||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
className="text-red-500"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setSelectedSite(siteRow);
|
||||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="text-red-500">Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -252,8 +251,8 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -261,6 +260,9 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
<CreateSiteForm
|
||||
open={isCreateModalOpen}
|
||||
setOpen={setIsCreateModalOpen}
|
||||
onCreate={(val) => {
|
||||
setRows([val, ...rows]);
|
||||
}}
|
||||
/>
|
||||
|
||||
{selectedSite && (
|
||||
@@ -302,12 +304,11 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
|
||||
<SitesDataTable
|
||||
columns={columns}
|
||||
data={sites}
|
||||
data={rows}
|
||||
addSite={() => {
|
||||
setIsCreateModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
{/* <button onClick={callApi}>Create Newt</button> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"use client";
|
||||
|
||||
import { createApiClient } from "@app/api";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from "@app/components/ui/card";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { AuthWithAccessTokenResponse } from "@server/routers/resource";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type AccessTokenProps = {
|
||||
accessTokenId: string | undefined;
|
||||
accessToken: string | undefined;
|
||||
resourceId: number;
|
||||
redirectUrl: string;
|
||||
};
|
||||
|
||||
export default function AccessToken({
|
||||
accessTokenId,
|
||||
accessToken,
|
||||
resourceId,
|
||||
redirectUrl
|
||||
}: AccessTokenProps) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
useEffect(() => {
|
||||
if (!accessTokenId || !accessToken) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
async function check() {
|
||||
try {
|
||||
const res = await api.post<
|
||||
AxiosResponse<AuthWithAccessTokenResponse>
|
||||
>(`/auth/resource/${resourceId}/access-token`, {
|
||||
accessToken,
|
||||
accessTokenId
|
||||
});
|
||||
|
||||
if (res.data.data.session) {
|
||||
window.location.href = redirectUrl;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error checking access token", e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
check();
|
||||
}, [accessTokenId, accessToken]);
|
||||
|
||||
return loading ? (
|
||||
<div></div>
|
||||
) : (
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center text-2xl font-bold">
|
||||
Access URL Invalid
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
This shared access URL is invalid. Please contact the resource
|
||||
owner for a new URL.
|
||||
<div className="text-center mt-4">
|
||||
<Button>
|
||||
<Link href="/">Go Home</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from "@app/components/ui/card";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function AccessTokenInvalid() {
|
||||
return (
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center text-2xl font-bold">
|
||||
Acess URL Invalid
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
This shared access URL is invalid. Please contact the resource
|
||||
owner for a new URL.
|
||||
<div className="text-center mt-4">
|
||||
<Button>
|
||||
<Link href="/">Go Home</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -14,7 +14,8 @@ import ResourceNotFound from "./components/ResourceNotFound";
|
||||
import ResourceAccessDenied from "./components/ResourceAccessDenied";
|
||||
import { cookies } from "next/headers";
|
||||
import { CheckResourceSessionResponse } from "@server/routers/auth";
|
||||
import AccessTokenInvalid from "./components/AccessTokenInvalid";
|
||||
import AccessTokenInvalid from "./components/AccessToken";
|
||||
import AccessToken from "./components/AccessToken";
|
||||
|
||||
export default async function ResourceAuthPage(props: {
|
||||
params: Promise<{ resourceId: number }>;
|
||||
@@ -50,35 +51,6 @@ export default async function ResourceAuthPage(props: {
|
||||
|
||||
const redirectUrl = searchParams.redirect || authInfo.url;
|
||||
|
||||
if (searchParams.token) {
|
||||
let doRedirect = false;
|
||||
try {
|
||||
const res = await internal.post<
|
||||
AxiosResponse<AuthWithAccessTokenResponse>
|
||||
>(
|
||||
`/auth/resource/${params.resourceId}/access-token`,
|
||||
{
|
||||
accessToken: searchParams.token
|
||||
},
|
||||
await authCookieHeader()
|
||||
);
|
||||
|
||||
if (res.data.data.session) {
|
||||
doRedirect = true;
|
||||
}
|
||||
} catch (e) {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<AccessTokenInvalid />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (doRedirect) {
|
||||
redirect(redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
const hasAuth =
|
||||
authInfo.password ||
|
||||
authInfo.pincode ||
|
||||
@@ -146,6 +118,20 @@ export default async function ResourceAuthPage(props: {
|
||||
}
|
||||
}
|
||||
|
||||
if (searchParams.token) {
|
||||
const [accessTokenId, accessToken] = searchParams.token.split(".");
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<AccessToken
|
||||
accessToken={accessToken}
|
||||
accessTokenId={accessTokenId}
|
||||
resourceId={params.resourceId}
|
||||
redirectUrl={redirectUrl}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{userIsUnauthorized && isSSOOnly ? (
|
||||
|
||||
Reference in New Issue
Block a user