Snyk report fixes + offcanvas sidebar fix (#263)

Refactor:
- Improved consistency and robustness by standardizing file encoding arguments from 'utf-8' to 'utf8' across various file read/write operations.
- Simplified status mapping logic in billing components and services by replacing switch statements with direct mapping objects for clearer and more maintainable code.
- Enhanced type conversion and error handling in billing and internationalization components for improved reliability.
- Updated sorting logic in team member tables for more predictable member ordering.
- Improved error logging with sanitized output to prevent formatting issues.
- Adjusted environment variable whitelisting to use a more flexible matching pattern.
- Fix variables for sidebar style handling

Style:
- Refined spacing and layout in account selector and sidebar header components for better visual consistency.
This commit is contained in:
Giancarlo Buomprisco
2025-06-01 19:10:39 +07:00
committed by GitHub
parent cb80e4fdcf
commit fc2fda595a
18 changed files with 72 additions and 116 deletions

View File

@@ -37,7 +37,7 @@ export async function updateTranslationAction(props: z.infer<typeof Schema>) {
try { try {
// Read the current translations file // Read the current translations file
const translationsFile = readFileSync(filePath, 'utf-8'); const translationsFile = readFileSync(filePath, 'utf8');
const translations = JSON.parse(translationsFile) as Record<string, any>; const translations = JSON.parse(translationsFile) as Record<string, any>;
// Update the nested key value // Update the nested key value
@@ -60,7 +60,7 @@ export async function updateTranslationAction(props: z.infer<typeof Schema>) {
current[finalKey] = value; current[finalKey] = value;
// Write the updated translations back to the file // Write the updated translations back to the file
writeFileSync(filePath, JSON.stringify(translations, null, 2), 'utf-8'); writeFileSync(filePath, JSON.stringify(translations, null, 2), 'utf8');
revalidatePath(`/translations`); revalidatePath(`/translations`);
@@ -95,7 +95,7 @@ export const translateWithAIAction = async (
if (!existsSync(filePath)) { if (!existsSync(filePath)) {
// create the file if it doesn't exist // create the file if it doesn't exist
writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf-8'); writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8');
} }
const results: Record<string, string> = {}; const results: Record<string, string> = {};

View File

@@ -265,12 +265,12 @@ export const envVariables: EnvVariableModel[] = [
{ {
name: 'NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE', name: 'NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE',
description: description:
'Defines sidebar collapse behavior. Options: offscreen, icon, or none.', 'Defines sidebar collapse behavior. Options: offcanvas, icon, or none.',
category: 'Navigation', category: 'Navigation',
type: 'enum', type: 'enum',
values: ['offscreen', 'icon', 'none'], values: ['offcanvas', 'icon', 'none'],
validate: ({ value }) => { validate: ({ value }) => {
return z.enum(['offscreen', 'icon', 'none']).optional().safeParse(value); return z.enum(['offcanvas', 'icon', 'none']).optional().safeParse(value);
}, },
}, },
{ {

View File

@@ -53,10 +53,10 @@ export async function updateEnvironmentVariableAction(
const filePath = `${root}/apps/web/${source}`; const filePath = `${root}/apps/web/${source}`;
if (!existsSync(filePath)) { if (!existsSync(filePath)) {
writeFileSync(filePath, '', 'utf-8'); writeFileSync(filePath, '', 'utf8');
} }
const sourceEnvFile = readFileSync(`${root}apps/web/${source}`, 'utf-8'); const sourceEnvFile = readFileSync(`${root}apps/web/${source}`, 'utf8');
let updatedEnvFile = ''; let updatedEnvFile = '';
const isInSourceFile = sourceEnvFile.includes(name); const isInSourceFile = sourceEnvFile.includes(name);
@@ -73,7 +73,7 @@ export async function updateEnvironmentVariableAction(
} }
// write the updated content back to the file // write the updated content back to the file
writeFileSync(`${root}/apps/web/${source}`, updatedEnvFile, 'utf-8'); writeFileSync(`${root}/apps/web/${source}`, updatedEnvFile, 'utf8');
revalidatePath(`/variables`); revalidatePath(`/variables`);

View File

@@ -43,5 +43,5 @@ export const personalAccountNavigationConfig = NavigationConfigSchema.parse({
routes, routes,
style: process.env.NEXT_PUBLIC_USER_NAVIGATION_STYLE, style: process.env.NEXT_PUBLIC_USER_NAVIGATION_STYLE,
sidebarCollapsed: process.env.NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED, sidebarCollapsed: process.env.NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED,
sidebarCollapsedStyle: process.env.NEXT_PUBLIC_SIDEBAR_COLLAPSED_STYLE, sidebarCollapsedStyle: process.env.NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE,
}); });

View File

@@ -49,7 +49,7 @@ export function getTeamAccountSidebarConfig(account: string) {
routes: getRoutes(account), routes: getRoutes(account),
style: process.env.NEXT_PUBLIC_TEAM_NAVIGATION_STYLE, style: process.env.NEXT_PUBLIC_TEAM_NAVIGATION_STYLE,
sidebarCollapsed: process.env.NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED, sidebarCollapsed: process.env.NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED,
sidebarCollapsedStyle: process.env.NEXT_PUBLIC_SIDEBAR_COLLAPSED_STYLE, sidebarCollapsedStyle: process.env.NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE,
}); });
} }

View File

@@ -2,43 +2,27 @@ import { Enums } from '@kit/supabase/database';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
const statusBadgeMap: Record<Enums<'subscription_status'>, `success` | `destructive` | `warning`> = {
active: 'success',
trialing: 'success',
past_due: 'destructive',
canceled: 'destructive',
unpaid: 'destructive',
incomplete: 'warning',
incomplete_expired: 'destructive',
paused: 'warning',
};
export function CurrentPlanAlert( export function CurrentPlanAlert(
props: React.PropsWithoutRef<{ props: React.PropsWithoutRef<{
status: Enums<'subscription_status'>; status: Enums<'subscription_status'>;
}>, }>,
) { ) {
let variant: 'success' | 'warning' | 'destructive';
const prefix = 'billing:status'; const prefix = 'billing:status';
const text = `${prefix}.${props.status}.description`; const text = `${prefix}.${props.status}.description`;
const title = `${prefix}.${props.status}.heading`; const title = `${prefix}.${props.status}.heading`;
const variant = statusBadgeMap[props.status];
switch (props.status) {
case 'active':
variant = 'success';
break;
case 'trialing':
variant = 'success';
break;
case 'past_due':
variant = 'destructive';
break;
case 'canceled':
variant = 'destructive';
break;
case 'unpaid':
variant = 'destructive';
break;
case 'incomplete':
variant = 'warning';
break;
case 'incomplete_expired':
variant = 'destructive';
break;
case 'paused':
variant = 'warning';
break;
}
return ( return (
<Alert variant={variant}> <Alert variant={variant}>

View File

@@ -4,43 +4,27 @@ import { Trans } from '@kit/ui/trans';
type Status = Enums<'subscription_status'> | Enums<'payment_status'>; type Status = Enums<'subscription_status'> | Enums<'payment_status'>;
const statusBadgeMap: Record<Status, `success` | `destructive` | `warning`> = {
active: 'success',
succeeded: 'success',
trialing: 'success',
past_due: 'destructive',
failed: 'destructive',
canceled: 'destructive',
unpaid: 'destructive',
incomplete: 'warning',
pending: 'warning',
incomplete_expired: 'destructive',
paused: 'warning',
}
export function CurrentPlanBadge( export function CurrentPlanBadge(
props: React.PropsWithoutRef<{ props: React.PropsWithoutRef<{
status: Status; status: Status;
}>, }>,
) { ) {
let variant: 'success' | 'warning' | 'destructive';
const text = `billing:status.${props.status}.badge`; const text = `billing:status.${props.status}.badge`;
const variant = statusBadgeMap[props.status];
switch (props.status) {
case 'active':
case 'succeeded':
variant = 'success';
break;
case 'trialing':
variant = 'success';
break;
case 'past_due':
case 'failed':
variant = 'destructive';
break;
case 'canceled':
variant = 'destructive';
break;
case 'unpaid':
variant = 'destructive';
break;
case 'incomplete':
case 'pending':
variant = 'warning';
break;
case 'incomplete_expired':
variant = 'destructive';
break;
case 'paused':
variant = 'warning';
break;
}
return ( return (
<Badge data-test={'current-plan-card-status-badge'} variant={variant}> <Badge data-test={'current-plan-card-status-badge'} variant={variant}>

View File

@@ -258,7 +258,7 @@ function Tiers({
i18nKey={'billing:andAbove'} i18nKey={'billing:andAbove'}
values={{ values={{
unit, unit,
previousTier: (previousTierFrom as number) - 1, previousTier: (Number(previousTierFrom) as number) - 1,
}} }}
/> />
</span> </span>

View File

@@ -1,4 +1,3 @@
import { BillingConfig } from '@kit/billing';
import { UpsertSubscriptionParams } from '@kit/billing/types'; import { UpsertSubscriptionParams } from '@kit/billing/types';
type SubscriptionStatus = type SubscriptionStatus =
@@ -23,8 +22,6 @@ export function createLemonSqueezySubscriptionPayloadBuilderService() {
* @description This class is used to build the subscription payload for Lemon Squeezy * @description This class is used to build the subscription payload for Lemon Squeezy
*/ */
class LemonSqueezySubscriptionPayloadBuilderService { class LemonSqueezySubscriptionPayloadBuilderService {
private config?: BillingConfig;
/** /**
* @name build * @name build
* @description Build the subscription payload for Lemon Squeezy * @description Build the subscription payload for Lemon Squeezy
@@ -103,24 +100,18 @@ class LemonSqueezySubscriptionPayloadBuilderService {
} }
private getSubscriptionStatus(status: SubscriptionStatus) { private getSubscriptionStatus(status: SubscriptionStatus) {
switch (status) { const statusMap = {
case 'active': active: 'active',
return 'active'; cancelled: 'canceled',
case 'cancelled': paused: 'paused',
return 'canceled'; on_trial: 'trialing',
case 'paused': past_due: 'past_due',
return 'paused'; unpaid: 'unpaid',
case 'on_trial': expired: 'past_due',
return 'trialing'; } satisfies Record<SubscriptionStatus, UpsertSubscriptionParams['status']>;
case 'past_due':
return 'past_due'; // Default to 'active' if status is unknown
case 'unpaid': return statusMap[status] || 'active';
return 'unpaid';
case 'expired':
return 'past_due';
default:
return 'active';
}
} }
} }

View File

@@ -447,19 +447,16 @@ export class LemonSqueezyWebhookHandlerService
return type; return type;
} }
private getOrderStatus(status: OrderStatus) { private getOrderStatus(status: OrderStatus) {
switch (status) { const statusMap = {
case 'paid': paid: 'succeeded',
return 'succeeded'; pending: 'pending',
case 'pending': failed: 'failed',
return 'pending'; refunded: 'failed',
case 'failed': } satisfies Record<OrderStatus, UpsertOrderParams['status']>
return 'failed';
case 'refunded': return statusMap[status] ?? 'pending';
return 'failed';
default:
return 'pending';
}
} }
} }

View File

@@ -100,7 +100,7 @@ export function AccountSelector({
role="combobox" role="combobox"
aria-expanded={open} aria-expanded={open}
className={cn( className={cn(
'dark:shadow-primary/10 group w-full min-w-0 px-2 lg:w-auto lg:max-w-fit', 'dark:shadow-primary/10 group w-full min-w-0 px-2 lg:w-auto lg:max-w-fit mr-1',
{ {
'justify-start': !collapsed, 'justify-start': !collapsed,
'm-auto justify-center px-2 lg:w-full': collapsed, 'm-auto justify-center px-2 lg:w-full': collapsed,
@@ -114,7 +114,7 @@ export function AccountSelector({
<span <span
className={cn('flex max-w-full items-center', { className={cn('flex max-w-full items-center', {
'justify-center gap-x-0': collapsed, 'justify-center gap-x-0': collapsed,
'gap-x-4': !collapsed, 'gap-x-2': !collapsed,
})} })}
> >
<PersonalAccountAvatar /> <PersonalAccountAvatar />
@@ -133,7 +133,7 @@ export function AccountSelector({
<span <span
className={cn('flex max-w-full items-center', { className={cn('flex max-w-full items-center', {
'justify-center gap-x-0': collapsed, 'justify-center gap-x-0': collapsed,
'gap-x-4': !collapsed, 'gap-x-2': !collapsed,
})} })}
> >
<Avatar className={'rounded-xs h-6 w-6'}> <Avatar className={'rounded-xs h-6 w-6'}>
@@ -158,7 +158,7 @@ export function AccountSelector({
</If> </If>
<CaretSortIcon <CaretSortIcon
className={cn('ml-2 h-4 w-4 shrink-0 opacity-50', { className={cn('ml-1 h-4 w-4 shrink-0 opacity-50', {
hidden: collapsed, hidden: collapsed,
})} })}
/> />

View File

@@ -92,7 +92,7 @@ export function AccountMembersTable({
}) })
.sort((prev, next) => { .sort((prev, next) => {
if (prev.primary_owner_user_id === prev.user_id) { if (prev.primary_owner_user_id === prev.user_id) {
return -1; return 0;
} }
if (prev.role_hierarchy_level < next.role_hierarchy_level) { if (prev.role_hierarchy_level < next.role_hierarchy_level) {

View File

@@ -128,7 +128,7 @@ export function parseAcceptLanguageHeader(
const trimmedLocale = locale.trim(); const trimmedLocale = locale.trim();
const numQ = Number(q.replace(/q ?=/, '')); const numQ = Number(q.replace(/q ?=/, ''));
return [isNaN(numQ) ? 0 : numQ, trimmedLocale]; return [Number.isNaN(numQ) ? 0 : numQ, trimmedLocale];
}) })
.sort(([q1], [q2]) => q2 - q1) // Sort by quality value in descending order .sort(([q1], [q2]) => q2 - q1) // Sort by quality value in descending order
.flatMap(([_, locale]) => { .flatMap(([_, locale]) => {

View File

@@ -244,7 +244,7 @@ function onError({
console.error( console.error(
{ {
error, error: JSON.stringify(error).replace(/["\\]/g, '\\$&'),
name: `auth.callback`, name: `auth.callback`,
}, },
`An error occurred while signing user in`, `An error occurred while signing user in`,

View File

@@ -392,7 +392,7 @@ const SidebarHeader: React.FC<React.ComponentPropsWithRef<'div'>> = ({
return ( return (
<div <div
data-sidebar="header" data-sidebar="header"
className={cn('flex flex-col gap-2 p-2', className)} className={cn('flex flex-col gap-2 p-2 group-data-[state=collapsed]:group-data-[collapsible=offcanvas]:hidden', className)}
{...props} {...props}
/> />
); );

View File

@@ -6,7 +6,7 @@ const whitelist = {
STRIPE_WEBHOOK_SECRET: [/whsec_*/], STRIPE_WEBHOOK_SECRET: [/whsec_*/],
EMAIL_PASSWORD: ['password'], EMAIL_PASSWORD: ['password'],
SUPABASE_DB_WEBHOOK_SECRET: ['WEBHOOKSECRET'], SUPABASE_DB_WEBHOOK_SECRET: ['WEBHOOKSECRET'],
SUPABASE_SERVICE_ROLE_KEY: ['eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU'], SUPABASE_SERVICE_ROLE_KEY: [/qQwv8Hdp7fsn3W0YpN81IU/],
}; };
// List of sensitive environment variables that should not be in .env files // List of sensitive environment variables that should not be in .env files

View File

@@ -48,7 +48,7 @@ async function checkLicense() {
try { try {
const makerkitConfig = const makerkitConfig =
JSON.parse( JSON.parse(
readFileSync(path.resolve(process.cwd(), '../../.makerkitrc'), 'utf-8'), readFileSync(path.resolve(process.cwd(), '../../.makerkitrc'), 'utf8'),
) || {}; ) || {};
if (makerkitConfig.projectName) { if (makerkitConfig.projectName) {

View File

@@ -14,7 +14,7 @@ export namespace generator {
} }
export function loadEnvironmentVariables(filePath: string) { export function loadEnvironmentVariables(filePath: string) {
const file = readFileSync(filePath, 'utf-8'); const file = readFileSync(filePath, 'utf8');
const vars = file.split('\n').filter((line) => line.trim() !== ''); const vars = file.split('\n').filter((line) => line.trim() !== '');
const env: Record<string, string> = {}; const env: Record<string, string> = {};