diff --git a/src/views/components/common/ScheduleListItem.tsx b/src/views/components/common/ScheduleListItem.tsx index 8d9ca413..d79038f9 100644 --- a/src/views/components/common/ScheduleListItem.tsx +++ b/src/views/components/common/ScheduleListItem.tsx @@ -11,16 +11,19 @@ import { RadioButton, Trash, } from '@phosphor-icons/react'; +import { background } from '@shared/messages'; import type { UserSchedule } from '@shared/types/UserSchedule'; import Text from '@views/components/common/Text/Text'; import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit'; import useSchedules from '@views/hooks/useSchedules'; +import { LONGHORN_DEVELOPERS_ADMINS, LONGHORN_DEVELOPERS_SWE } from '@views/lib/getGitHubStats'; import clsx from 'clsx'; import React, { useEffect, useMemo, useState } from 'react'; import { Button } from './Button'; import DialogProvider, { usePrompt } from './DialogProvider/DialogProvider'; import { ExtensionRootWrapper, styleResetClass } from './ExtensionRoot/ExtensionRoot'; +import Link from './Link'; import { SortableListDragHandle } from './SortableListDragHandle'; /** @@ -32,6 +35,7 @@ interface ScheduleListItemProps { } const IS_STORYBOOK = import.meta.env.STORYBOOK; +const teamMembers = [...LONGHORN_DEVELOPERS_ADMINS, ...LONGHORN_DEVELOPERS_SWE]; /** * This is a reusable dropdown component that can be used to toggle the visiblity of information @@ -40,6 +44,7 @@ export default function ScheduleListItem({ schedule, onClick }: ScheduleListItem const [activeSchedule] = useSchedules(); const [isEditing, setIsEditing] = useState(false); const [editorValue, setEditorValue] = useState(schedule.name); + const teamMember = teamMembers[Math.floor(Math.random() * teamMembers.length)]; const showDialog = usePrompt(); const enforceScheduleLimit = useEnforceScheduleLimit(); @@ -65,13 +70,46 @@ export default function ScheduleListItem({ schedule, onClick }: ScheduleListItem const handleBlur = async () => { if (editorValue.trim() !== '' && editorValue.trim() !== schedule.name) { schedule.name = (await renameSchedule(schedule.id, editorValue.trim())) as string; + + if (schedule.name === '404') { + const url = chrome.runtime.getURL('/404.html'); + background.openNewTab({ url }); + } + + if (Math.random() < 0.002) { + showDialog({ + title: 'Schedule name already taken', + description: ( + <> + Schedule name + {schedule.name} + + is already taken. +
+
+ Join the  +
+ + Discord + + to contact {teamMember?.name as string}. + + ), + // eslint-disable-next-line react/no-unstable-nested-components + buttons: close => ( + + ), + }); + } } setIsEditing(false); }; const handleDelete = () => { showDialog({ - title: `Are you sure?`, + title: 'Are you sure?', description: ( <> Deleting diff --git a/utils/plugins/vite-build-logger.ts b/utils/plugins/vite-build-logger.ts new file mode 100644 index 00000000..3e5c11c2 --- /dev/null +++ b/utils/plugins/vite-build-logger.ts @@ -0,0 +1,81 @@ +import chalk from 'chalk'; +import type { Plugin } from 'vite'; + +/** + * Options to configure the build logger. + */ +interface BuildLoggerOptions { + includeEnvVars?: string[]; // Specific env vars to display + includeTimestamp?: boolean; + includeBuildTime?: boolean; + customMetadata?: Record string | Promise>; +} + +/** + * Vite plugin to log build information. + * + * @param Options - to configure the build logger. + * + * @returns Vite plugin object. + */ +export function buildLogger(options: BuildLoggerOptions = {}): Plugin { + const startTime = Date.now(); + + return { + name: 'vite-build-logger', + enforce: 'post', + + async closeBundle() { + console.log(`\n${chalk.bold.cyan('=== Build Information ===')}`); + + // Environment + console.log(chalk.yellow('\nBuild Environment:')); + console.log(`🔧 Node Build Mode: ${process.env.NODE_ENV}`); + console.log(`🎯 Browser Target: ${process.env.BROWSER_TARGET}`); + + // Timestamp + if (options.includeTimestamp) { + console.log(chalk.yellow('\nBuild Timestamp:')); + console.log(`📅 ${new Date().toISOString()}`); + } + + // Build Time + if (options.includeBuildTime) { + const buildTime = Date.now() - startTime; + console.log(chalk.yellow('\nBuild Time:')); + console.log(`⏱️ ${buildTime}ms (${(buildTime / 1000).toFixed(2)}s)`); + } + + // Selected Environment Variables + if (options.includeEnvVars?.length) { + console.log(chalk.yellow('\nEnvironment Variables:')); + for (const envVar of options.includeEnvVars) { + if (process.env[envVar]) { + // Mask sensitive variables that might contain tokens or keys + const isSensitive = + envVar.toLowerCase().includes('key') || + envVar.toLowerCase().includes('token') || + envVar.toLowerCase().includes('secret'); + + const value = isSensitive ? '****' : process.env[envVar]; + console.log(`${envVar}: ${value}`); + } else { + console.log(`${envVar}: ${chalk.red('Not defined')}`); + } + } + } + + // Custom Metadata + if (options.customMetadata) { + console.log(chalk.yellow('\nCustom Metadata:')); + for (const [key, getter] of Object.entries(options.customMetadata)) { + // eslint-disable-next-line no-await-in-loop + const value = await getter(); + console.log(`${key}: ${value}`); + } + } + + console.log(`\n${chalk.bold.cyan('=====================')}\n`); + }, + }; +} diff --git a/vite.config.ts b/vite.config.ts index db93c2b3..71cf663d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,7 @@ /// import { crx } from '@crxjs/vite-plugin'; import react from '@vitejs/plugin-react-swc'; +import { execSync } from 'child_process'; import { resolve } from 'path'; import UnoCSS from 'unocss/vite'; import Icons from 'unplugin-icons/vite'; @@ -11,17 +12,27 @@ import inspect from 'vite-plugin-inspect'; import packageJson from './package.json'; import manifest from './src/manifest'; import vitePluginRunCommandOnDemand from './utils/plugins/run-command-on-demand'; +import { buildLogger } from './utils/plugins/vite-build-logger'; + +const BROWSER_TARGET = process.env.BROWSER_TARGET || 'chrome'; + +// Set browser target environment variable default +process.env.BROWSER_TARGET = BROWSER_TARGET; const root = resolve(__dirname, 'src'); const pagesDir = resolve(root, 'pages'); const assetsDir = resolve(root, 'assets'); const publicDir = resolve(__dirname, 'public'); +// Set default environment variables +process.env.PROD = process.env.NODE_ENV === 'production' ? 'true' : 'false'; + const isBeta = !!process.env.BETA; if (isBeta) { process.env.VITE_BETA_BUILD = 'true'; } process.env.VITE_PACKAGE_VERSION = packageJson.version; +// TODO: Debug this. If PROD is false, VITE_SENTRY_ENVIRONMENT is in production mode if (process.env.PROD) { process.env.VITE_SENTRY_ENVIRONMENT = 'production'; } else if (isBeta) { @@ -162,6 +173,23 @@ export default defineConfig({ // afterServerStart: 'pnpm gulp forceDisableUseDynamicUrl', closeBundle: 'pnpm gulp forceDisableUseDynamicUrl', }), + buildLogger({ + includeEnvVars: [ + 'VITE_PACKAGE_VERSION', + 'NODE_ENV', + 'BROWSER_TARGET', + 'PROD', + 'VITE_SENTRY_ENVIRONMENT', + 'VITE_BETA_BUILD', + ], + includeTimestamp: true, + includeBuildTime: true, + customMetadata: { + gitBranch: () => execSync('git rev-parse --abbrev-ref HEAD').toString().trim(), + gitCommit: () => execSync('git rev-parse --short HEAD').toString().trim(), + nodeVersion: () => process.version, + }, + }), ], resolve: { alias: { @@ -205,6 +233,8 @@ export default defineConfig({ }, build: { target: ['chrome120', 'edge120', 'firefox120'], + // NOTE: Eventually we will add this back once we support multiple browsers + // outDir: `dist/${process.env.BROWSER_TARGET || 'chrome'}`, emptyOutDir: true, reportCompressedSize: false, sourcemap: true,