mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-04 18:14:53 +02:00
improve site and resource info cards and other small visual tweaks
This commit is contained in:
parent
e6263567a9
commit
172e0f07d5
31 changed files with 469 additions and 332 deletions
|
@ -33,10 +33,20 @@ export const ConfirmPasswordReset = ({ email }: Props) => {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<Body className="font-sans">
|
||||
<Body className="font-sans relative">
|
||||
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm font-bold text-orange-500">
|
||||
Pangolin
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-500">
|
||||
{new Date().toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Heading className="text-2xl font-semibold text-gray-800 text-center">
|
||||
Your password has been successfully reset
|
||||
Password Reset Confirmation
|
||||
</Heading>
|
||||
<Text className="text-base text-gray-700 mt-4">
|
||||
Hi {email || "there"},
|
||||
|
@ -46,12 +56,10 @@ export const ConfirmPasswordReset = ({ email }: Props) => {
|
|||
reset. If you made this change, no further action is
|
||||
required.
|
||||
</Text>
|
||||
<Section className="text-center my-6">
|
||||
<Text className="text-base text-gray-700">
|
||||
If you did not request this change, please
|
||||
contact our support team immediately.
|
||||
</Text>
|
||||
</Section>
|
||||
<Text className="text-base text-gray-700">
|
||||
If you did not request this change, please contact
|
||||
our support team immediately.
|
||||
</Text>
|
||||
<Text className="text-base text-gray-700 mt-2">
|
||||
Thank you for keeping your account secure.
|
||||
</Text>
|
||||
|
|
|
@ -37,8 +37,18 @@ export const ResetPasswordCode = ({ email, code, link }: Props) => {
|
|||
>
|
||||
<Body className="font-sans">
|
||||
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm font-bold text-orange-500">
|
||||
Pangolin
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-500">
|
||||
{new Date().toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Heading className="text-2xl font-semibold text-gray-800 text-center">
|
||||
You've requested to reset your password
|
||||
Password Reset Request
|
||||
</Heading>
|
||||
<Text className="text-base text-gray-700 mt-4">
|
||||
Hi {email || "there"},
|
||||
|
@ -51,7 +61,7 @@ export const ResetPasswordCode = ({ email, code, link }: Props) => {
|
|||
and follow the instructions to reset your password,
|
||||
or manually enter the following code:
|
||||
</Text>
|
||||
<Section className="text-center my-6">
|
||||
<Section className="text-center">
|
||||
<Text className="inline-block bg-primary text-xl font-bold text-white py-2 px-4 border border-gray-300 rounded-xl">
|
||||
{code}
|
||||
</Text>
|
||||
|
|
|
@ -43,6 +43,16 @@ export const ResourceOTPCode = ({
|
|||
>
|
||||
<Body className="font-sans">
|
||||
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm font-bold text-orange-500">
|
||||
Pangolin
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-500">
|
||||
{new Date().toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Heading className="text-2xl font-semibold text-gray-800 text-center">
|
||||
Your One-Time Password
|
||||
</Heading>
|
||||
|
@ -56,12 +66,11 @@ export const ResourceOTPCode = ({
|
|||
<strong>{organizationName}</strong>. Use the OTP
|
||||
below to complete your authentication:
|
||||
</Text>
|
||||
<Section className="text-center my-6">
|
||||
<Section className="text-center">
|
||||
<Text className="inline-block bg-primary text-xl font-bold text-white py-2 px-4 border border-gray-300 rounded-xl">
|
||||
{otp}
|
||||
</Text>
|
||||
</Section>
|
||||
|
||||
<Text className="text-sm text-gray-500 mt-6">
|
||||
Best regards,
|
||||
<br />
|
||||
|
|
|
@ -46,8 +46,18 @@ export const SendInviteLink = ({
|
|||
>
|
||||
<Body className="font-sans">
|
||||
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm font-bold text-orange-500">
|
||||
Pangolin
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-500">
|
||||
{new Date().toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Heading className="text-2xl font-semibold text-gray-800 text-center">
|
||||
You're invited to join a Fossorial organization
|
||||
You're Invite to Join {orgName}
|
||||
</Heading>
|
||||
<Text className="text-base text-gray-700 mt-4">
|
||||
Hi {email || "there"},
|
||||
|
@ -65,12 +75,12 @@ export const SendInviteLink = ({
|
|||
{expiresInDays === "1" ? "day" : "days"}.
|
||||
</b>
|
||||
</Text>
|
||||
<Section className="text-center my-6">
|
||||
<Section className="text-center">
|
||||
<Button
|
||||
href={inviteLink}
|
||||
className="rounded-lg bg-primary px-[12px] py-[9px] text-center font-semibold text-white cursor-pointer text-xl"
|
||||
>
|
||||
Accept invitation to {orgName}
|
||||
Accept Invite to {orgName}
|
||||
</Button>
|
||||
</Section>
|
||||
|
||||
|
|
|
@ -36,6 +36,16 @@ export const TwoFactorAuthNotification = ({ email, enabled }: Props) => {
|
|||
>
|
||||
<Body className="font-sans">
|
||||
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm font-bold text-orange-500">
|
||||
Pangolin
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-500">
|
||||
{new Date().toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Heading className="text-2xl font-semibold text-gray-800 text-center">
|
||||
Two-Factor Authentication{" "}
|
||||
{enabled ? "Enabled" : "Disabled"}
|
||||
|
@ -48,22 +58,19 @@ export const TwoFactorAuthNotification = ({ email, enabled }: Props) => {
|
|||
has been successfully{" "}
|
||||
{enabled ? "enabled" : "disabled"} on your account.
|
||||
</Text>
|
||||
<Section className="text-center my-6">
|
||||
{enabled ? (
|
||||
<Text className="text-base text-gray-700">
|
||||
With Two-Factor Authentication enabled, your
|
||||
account is now more secure. Please ensure
|
||||
you keep your authentication method safe.
|
||||
</Text>
|
||||
) : (
|
||||
<Text className="text-base text-gray-700">
|
||||
With Two-Factor Authentication disabled,
|
||||
your account may be less secure. We
|
||||
recommend enabling it to protect your
|
||||
account.
|
||||
</Text>
|
||||
)}
|
||||
</Section>
|
||||
{enabled ? (
|
||||
<Text className="text-base text-gray-700">
|
||||
With Two-Factor Authentication enabled, your
|
||||
account is now more secure. Please ensure you
|
||||
keep your authentication method safe.
|
||||
</Text>
|
||||
) : (
|
||||
<Text className="text-base text-gray-700">
|
||||
With Two-Factor Authentication disabled, your
|
||||
account may be less secure. We recommend
|
||||
enabling it to protect your account.
|
||||
</Text>
|
||||
)}
|
||||
<Text className="text-base text-gray-700 mt-2">
|
||||
If you did not make this change, please contact our
|
||||
support team immediately.
|
||||
|
|
|
@ -41,17 +41,28 @@ export const VerifyEmail = ({
|
|||
>
|
||||
<Body className="font-sans">
|
||||
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm font-bold text-orange-500">
|
||||
Pangolin
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-500">
|
||||
{new Date().toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Heading className="text-2xl font-semibold text-gray-800 text-center">
|
||||
Please verify your email
|
||||
Please Verify Your Email
|
||||
</Heading>
|
||||
<Text className="text-base text-gray-700 mt-4">
|
||||
Hi {username || "there"},
|
||||
</Text>
|
||||
<Text className="text-base text-gray-700 mt-2">
|
||||
You’ve requested to verify your email. Please use
|
||||
the code below to complete the verification process upon logging in.
|
||||
the code below to complete the verification process
|
||||
upon logging in.
|
||||
</Text>
|
||||
<Section className="text-center my-6">
|
||||
<Section className="text-center">
|
||||
<Text className="inline-block bg-primary text-xl font-bold text-white py-2 px-4 border border-gray-300 rounded-xl">
|
||||
{verificationCode}
|
||||
</Text>
|
||||
|
|
|
@ -25,7 +25,12 @@ const createSiteSchema = z
|
|||
.object({
|
||||
name: z.string().min(1).max(255),
|
||||
exitNodeId: z.number().int().positive(),
|
||||
subdomain: z.string().min(1).max(255).optional(),
|
||||
// subdomain: z
|
||||
// .string()
|
||||
// .min(1)
|
||||
// .max(255)
|
||||
// .transform((val) => val.toLowerCase())
|
||||
// .optional(),
|
||||
pubKey: z.string().optional(),
|
||||
subnet: z.string(),
|
||||
newtId: z.string().optional(),
|
||||
|
|
|
@ -23,13 +23,25 @@ const getSiteSchema = z
|
|||
})
|
||||
.strict();
|
||||
|
||||
export type GetSiteResponse = {
|
||||
siteId: number;
|
||||
name: string;
|
||||
subdomain: string;
|
||||
subnet: string;
|
||||
type: string;
|
||||
};
|
||||
async function query(siteId?: number, niceId?: string, orgId?: string) {
|
||||
if (siteId) {
|
||||
const [res] = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.siteId, siteId))
|
||||
.limit(1);
|
||||
return res;
|
||||
} else if (niceId && orgId) {
|
||||
const [res] = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(and(eq(sites.niceId, niceId), eq(sites.orgId, orgId)))
|
||||
.limit(1);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export type GetSiteResponse = NonNullable<Awaited<ReturnType<typeof query>>>;
|
||||
|
||||
export async function getSite(
|
||||
req: Request,
|
||||
|
@ -49,42 +61,14 @@ export async function getSite(
|
|||
|
||||
const { siteId, niceId, orgId } = parsedParams.data;
|
||||
|
||||
let site;
|
||||
if (siteId) {
|
||||
site = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.siteId, siteId))
|
||||
.limit(1);
|
||||
} else if (niceId && orgId) {
|
||||
site = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(and(eq(sites.niceId, niceId), eq(sites.orgId, orgId)))
|
||||
.limit(1);
|
||||
}
|
||||
const site = await query(siteId, niceId, orgId);
|
||||
|
||||
if (!site) {
|
||||
return next(createHttpError(HttpCode.NOT_FOUND, "Site not found"));
|
||||
}
|
||||
|
||||
if (site.length === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Site with ID ${siteId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return response(res, {
|
||||
data: {
|
||||
siteId: site[0].siteId,
|
||||
niceId: site[0].niceId,
|
||||
name: site[0].name,
|
||||
subnet: site[0].subnet,
|
||||
type: site[0].type
|
||||
},
|
||||
return response<GetSiteResponse>(res, {
|
||||
data: site,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Site retrieved successfully",
|
||||
|
|
|
@ -18,7 +18,12 @@ const updateSiteParamsSchema = z
|
|||
const updateSiteBodySchema = z
|
||||
.object({
|
||||
name: z.string().min(1).max(255).optional(),
|
||||
subdomain: z.string().min(1).max(255).optional()
|
||||
// subdomain: z
|
||||
// .string()
|
||||
// .min(1)
|
||||
// .max(255)
|
||||
// .transform((val) => val.toLowerCase())
|
||||
// .optional()
|
||||
// pubKey: z.string().optional(),
|
||||
// subnet: z.string().optional(),
|
||||
// exitNode: z.number().int().positive().optional(),
|
||||
|
|
|
@ -6,4 +6,5 @@ export const subdomainSchema = z
|
|||
/^(?!:\/\/)([a-zA-Z0-9-_]+\.)*[a-zA-Z0-9-_]+$/,
|
||||
"Invalid subdomain format"
|
||||
)
|
||||
.min(1, "Subdomain must be at least 1 character long");
|
||||
.min(1, "Subdomain must be at least 1 character long")
|
||||
.transform((val) => val.toLowerCase());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue