'use client'; import { Fragment, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { envVariables } from '@/app/variables/lib/env-variables-model'; import { EnvModeSelector } from '@/components/env-mode-selector'; import { ChevronDown, ChevronUp, ChevronsUpDownIcon, Copy, Eye, EyeOff, InfoIcon, } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, } from '@kit/ui/dropdown-menu'; import { Heading } from '@kit/ui/heading'; import { If } from '@kit/ui/if'; import { Input } from '@kit/ui/input'; import { toast } from '@kit/ui/sonner'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@kit/ui/tooltip'; import { cn } from '@kit/ui/utils'; import { AppEnvState, EnvVariableState } from '../lib/types'; export function AppEnvironmentVariablesManager({ state, }: React.PropsWithChildren<{ state: AppEnvState; }>) { return (
Application: {state.appName}
); } function EnvList({ appState }: { appState: AppEnvState }) { const [expandedVars, setExpandedVars] = useState>({}); const [showValues, setShowValues] = useState>({}); const [search, setSearch] = useState(''); const searchParams = useSearchParams(); const secretVars = searchParams.get('secret') === 'true'; const publicVars = searchParams.get('public') === 'true'; const privateVars = searchParams.get('private') === 'true'; const overriddenVars = searchParams.get('overridden') === 'true'; const invalidVars = searchParams.get('invalid') === 'true'; const toggleExpanded = (key: string) => { setExpandedVars((prev) => ({ ...prev, [key]: !prev[key], })); }; const toggleShowValue = (key: string) => { setShowValues((prev) => ({ ...prev, [key]: !prev[key], })); }; const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text); } catch (err) { console.error('Failed to copy:', err); } }; const renderValue = (value: string, isVisible: boolean) => { if (!isVisible) { return '••••••••'; } return value || '(empty)'; }; const renderVariable = (varState: EnvVariableState) => { const isExpanded = expandedVars[varState.key] ?? false; const isClientBundledValue = varState.key.startsWith('NEXT_PUBLIC_'); // public variables are always visible const isValueVisible = showValues[varState.key] ?? isClientBundledValue; // grab model is it's a kit variable const model = envVariables.find( (variable) => variable.name === varState.key, ); const allVariables = Object.values(appState.variables).reduce( (acc, variable) => ({ ...acc, [variable.key]: variable.effectiveValue, }), {}, ); const validation = model?.validate ? model.validate({ value: varState.effectiveValue, variables: allVariables, mode: appState.mode, }) : { success: true, error: undefined, }; const canExpand = varState.definitions.length > 1 || !validation.success; return (
{varState.key} {varState.isOverridden && ( Overridden )}
{(model) => (
{model.description}
)}
{renderValue(varState.effectiveValue, isValueVisible)}
{canExpand && ( )}
{isClientBundledValue ? `Public variable` : `Private variable`} {isClientBundledValue ? `This variable will be bundled into the client side. If this is a private variable, do not use "NEXT_PUBLIC".` : `This variable is private and will not be bundled client side, so you cannot access it from React components rendered client side`} Secret Variable This is a secret key. Keep it safe! {varState.effectiveSource} {varState.effectiveSource === '.env.local' ? `These variables are specific to this machine and are not committed` : varState.effectiveSource === '.env.development' ? `These variables are only being used during development` : varState.effectiveSource === '.env' ? `These variables are shared under all modes` : `These variables are only used in production mode`} Overridden in {varState.effectiveSource} This variable was overridden by a variable in{' '} {varState.effectiveSource} Invalid Value This variable has an invalid value. Drop down to view the errors.
{isExpanded && canExpand && (
Errors Invalid Value The value for {varState.key} is invalid:
                      {JSON.stringify(validation, null, 2)}
                    
1}>
Override Chain
{varState.definitions.map((def) => (
{def.source}
{renderValue(def.value, isValueVisible)}
))}
)}
); }; const filterVariable = (varState: EnvVariableState) => { const model = envVariables.find( (variable) => variable.name === varState.key, ); if ( !search && !secretVars && !publicVars && !privateVars && !invalidVars && !overriddenVars ) { return true; } const isSecret = model?.secret; const isPublic = varState.key.startsWith('NEXT_PUBLIC_'); const isPrivate = !isPublic; const isInSearch = search ? varState.key.toLowerCase().includes(search.toLowerCase()) : true; if (isPublic && publicVars && isInSearch) { return true; } if (isSecret && secretVars && isInSearch) { return true; } if (isPrivate && privateVars && isInSearch) { return true; } if (overriddenVars && varState.isOverridden && isInSearch) { return true; } if (invalidVars) { const allVariables = Object.values(appState.variables).reduce( (acc, variable) => ({ ...acc, [variable.key]: variable.effectiveValue, }), {}, ); const hasError = model && model.validate ? !model.validate({ value: varState.effectiveValue, variables: allVariables, mode: appState.mode, }).success : false; if (hasError && isInSearch) return true; } return false; }; const groups = Object.values(appState.variables) .filter(filterVariable) .reduce( (acc, variable) => { const group = acc.find((group) => group.category === variable.category); if (!group) { acc.push({ category: variable.category, variables: [variable], }); } else { group.variables.push(variable); } return acc; }, [] as Array<{ category: string; variables: Array }>, ); return (
setSearch(e.target.value)} /> Create a report from the environment variables. Useful for creating support tickets.
{groups.map((group) => (
{group.category}
{group.variables.map((item) => { return ( {renderVariable(item)} ); })}
))}
No variables found
); } function createReportFromEnvState(state: AppEnvState) { let report = ``; for (const key in state.variables) { const variable = state.variables[key]; const variableReport = `${key}: ${JSON.stringify(variable, null, 2)}`; ``; report += variableReport + '\n'; } return report; } function FilterSwitcher(props: { filters: { secret: boolean; public: boolean; overridden: boolean; private: boolean; invalid: boolean; }; }) { const router = useRouter(); const secretVars = props.filters.secret; const publicVars = props.filters.public; const overriddenVars = props.filters.overridden; const privateVars = props.filters.private; const invalidVars = props.filters.invalid; const handleFilterChange = (key: string, value: boolean) => { const searchParams = new URLSearchParams(window.location.search); const path = window.location.pathname; if (key === 'all' && value) { searchParams.delete('secret'); searchParams.delete('public'); searchParams.delete('overridden'); searchParams.delete('private'); searchParams.delete('invalid'); } else { if (!value) { searchParams.delete(key); } else { searchParams.set(key, 'true'); } } router.push(`${path}?${searchParams.toString()}`); }; const buttonLabel = () => { const filters = []; if (secretVars) filters.push('Secret'); if (publicVars) filters.push('Public'); if (overriddenVars) filters.push('Overridden'); if (privateVars) filters.push('Private'); if (invalidVars) filters.push('Invalid'); if (filters.length === 0) return 'Filter variables'; return filters.join(', '); }; const allSelected = !secretVars && !publicVars && !overriddenVars && !invalidVars; return ( { handleFilterChange('all', true); }} > All { handleFilterChange('secret', !secretVars); }} > Secret { handleFilterChange('private', !privateVars); }} > Private { handleFilterChange('public', !publicVars); }} > Public { handleFilterChange('invalid', !invalidVars); }} > Invalid { handleFilterChange('overridden', !overriddenVars); }} > Overridden ); } function Summary({ appState }: { appState: AppEnvState }) { const varsArray = Object.values(appState.variables); const overridden = varsArray.filter((variable) => variable.isOverridden); const allVariables = varsArray.reduce( (acc, variable) => ({ ...acc, [variable.key]: variable.effectiveValue, }), {}, ); const errors = varsArray.filter((variable) => { const model = envVariables.find((v) => variable.key === v.name); const validation = model && model.validate ? model.validate({ value: variable.effectiveValue, variables: allVariables, mode: appState.mode, }) : { success: true, }; return !validation.success; }); return (
{errors.length} Errors {overridden.length} Overridden Variables
); }