Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: development helper in env hint #537

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/app/Document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

import { Providers } from '@/app/Providers';
import { Viewport } from '@/components/Viewport';
import { EnvHint } from '@/features/devtools/EnvHint';
import { env } from '@/env.mjs';
import { AppHint } from '@/features/devtools/AppHint';
import { useLocale } from '@/lib/i18n/useLocale';
import { TrpcProvider } from '@/lib/trpc/TrpcProvider';
import theme, { COLOR_MODE_STORAGE_KEY } from '@/theme';
Expand Down Expand Up @@ -65,8 +66,10 @@ export const Document = ({ children }: { children: ReactNode }) => {
<Providers>
<TrpcProvider>
<Viewport>{children}</Viewport>
<EnvHint />
<ReactQueryDevtools initialIsOpen={false} />
<AppHint />
{env.NEXT_PUBLIC_DEV_TOOLS_ENABLED && (
<ReactQueryDevtools initialIsOpen={false} />
)}
</TrpcProvider>
</Providers>
</body>
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Metadata } from 'next';

import { Document } from '@/app/Document';
import { NextLoader } from '@/app/NextLoader';
import { getEnvHintTitlePrefix } from '@/features/devtools/EnvHint';
import { getEnvHintTitlePrefix } from '@/features/devtools/AppHint';

export const metadata: Metadata = {
title: {
Expand Down
46 changes: 46 additions & 0 deletions src/components/DarkModeSwitch/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useId } from 'react';

import {
FormControl,
FormLabel,
HStack,
Switch,
useColorMode,
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';

export const DarkModeSwitch = () => {
const { t } = useTranslation(['account']);
const { colorMode, setColorMode } = useColorMode();
const id = useId();

return (
<FormControl display="flex" alignItems="center">
<HStack>
<FormLabel
as={colorMode === 'light' ? 'span' : undefined}
opacity={colorMode !== 'light' ? 0.5 : undefined}
htmlFor={id}
mb="0"
mr={0}
>
{t('account:preferences.theme.light')}
</FormLabel>
<Switch
colorScheme="brand"
id={id}
isChecked={colorMode === 'dark'}
onChange={(e) => setColorMode(e.target.checked ? 'dark' : 'light')}
/>
<FormLabel
as={colorMode === 'dark' ? 'span' : undefined}
opacity={colorMode !== 'dark' ? 0.5 : undefined}
htmlFor={id}
mb="0"
>
{t('account:preferences.theme.dark')}
</FormLabel>
</HStack>
</FormControl>
);
};
11 changes: 11 additions & 0 deletions src/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ export const env = createEnv({
value ??
(process.env.NODE_ENV === 'development' ? 'warning' : 'success')
),
NEXT_PUBLIC_DEV_TOOLS_ENABLED: z
.enum(['true', 'false'])
.optional()
.transform((value) => {
if (value) {
return value === 'true';
}

return process.env.NODE_ENV === 'development' ? true : false;
}),
NEXT_PUBLIC_NODE_ENV: zNodeEnv(),
},

Expand All @@ -83,6 +93,7 @@ export const env = createEnv({
NEXT_PUBLIC_ENV_COLOR_SCHEME: process.env.NEXT_PUBLIC_ENV_COLOR_SCHEME,
NEXT_PUBLIC_ENV_NAME: process.env.NEXT_PUBLIC_ENV_NAME,
NEXT_PUBLIC_ENV_EMOJI: process.env.NEXT_PUBLIC_ENV_EMOJI,
NEXT_PUBLIC_DEV_TOOLS_ENABLED: process.env.NEXT_PUBLIC_DEV_TOOLS_ENABLED,
NEXT_PUBLIC_IS_DEMO: process.env.NEXT_PUBLIC_IS_DEMO,
NEXT_PUBLIC_NODE_ENV: process.env.NODE_ENV,
},
Expand Down
44 changes: 2 additions & 42 deletions src/features/account/PageAccount.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import React, { useId } from 'react';
import React from 'react';

import {
Alert,
AlertTitle,
Box,
Button,
Divider,
FormControl,
FormLabel,
HStack,
Heading,
Link,
LinkBox,
LinkOverlay,
Stack,
Switch,
useColorMode,
} from '@chakra-ui/react';
import { useRouter } from 'next/navigation';
import { useTranslation } from 'react-i18next';
import { LuArrowRight, LuLogOut, LuUser } from 'react-icons/lu';

import { ConfirmModal } from '@/components/ConfirmModal';
import { DarkModeSwitch } from '@/components/DarkModeSwitch';
import { Icon } from '@/components/Icons';
import { AccountDeleteButton } from '@/features/account/AccountDeleteButton';
import { AccountEmailForm } from '@/features/account/AccountEmailForm';
Expand Down Expand Up @@ -117,39 +113,3 @@ export default function PageHome() {
</AppLayoutPage>
);
}

const DarkModeSwitch = () => {
const { t } = useTranslation(['account']);
const { colorMode, setColorMode } = useColorMode();
const id = useId();

return (
<FormControl display="flex" alignItems="center">
<HStack>
<FormLabel
as={colorMode === 'light' ? 'span' : undefined}
opacity={colorMode !== 'light' ? 0.5 : undefined}
htmlFor={id}
mb="0"
mr={0}
>
{t('account:preferences.theme.light')}
</FormLabel>
<Switch
colorScheme="brand"
id={id}
isChecked={colorMode === 'dark'}
onChange={(e) => setColorMode(e.target.checked ? 'dark' : 'light')}
/>
<FormLabel
as={colorMode === 'dark' ? 'span' : undefined}
opacity={colorMode !== 'dark' ? 0.5 : undefined}
htmlFor={id}
mb="0"
>
{t('account:preferences.theme.dark')}
</FormLabel>
</HStack>
</FormControl>
);
};
82 changes: 82 additions & 0 deletions src/features/devtools/AppHint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Box, HStack, Text, useDisclosure } from '@chakra-ui/react';
import { LuAppWindow, LuPanelLeftOpen, LuPencilRuler } from 'react-icons/lu';

import { env } from '@/env.mjs';
import { DevToolsDrawer } from '@/features/devtools/DevToolsDrawer';

export const getEnvHintTitlePrefix = () => {
if (env.NEXT_PUBLIC_ENV_EMOJI) return `${env.NEXT_PUBLIC_ENV_EMOJI} `;
if (env.NEXT_PUBLIC_ENV_NAME) return `[${env.NEXT_PUBLIC_ENV_NAME}] `;
return '';
};

export const AppHint = () => {
if (!env.NEXT_PUBLIC_ENV_NAME && !env.NEXT_PUBLIC_DEV_TOOLS_ENABLED) {
return null;
}

return (
<Box
zIndex="9999"
position="fixed"
top="0"
insetStart="0"
insetEnd="0"
h="2px"
bg={`${env.NEXT_PUBLIC_ENV_COLOR_SCHEME}.400`}
>
<HStack
position="fixed"
top="0"
insetStart="4"
fontSize="0.6rem"
fontWeight="bold"
alignItems="start"
textTransform="uppercase"
>
{env.NEXT_PUBLIC_ENV_NAME && <EnvHint />}
{env.NEXT_PUBLIC_DEV_TOOLS_ENABLED && <DevToolsHint />}
</HStack>
</Box>
);
};

const EnvHint = () => {
return (
<Text
bg={`${env.NEXT_PUBLIC_ENV_COLOR_SCHEME}.400`}
color={`${env.NEXT_PUBLIC_ENV_COLOR_SCHEME}.900`}
px="1"
borderBottomStartRadius="sm"
borderBottomEndRadius="sm"
>
{env.NEXT_PUBLIC_ENV_NAME}
</Text>
);
};
const DevToolsHint = () => {
const devToolsDrawer = useDisclosure();

return (
<>
<Text
as="button"
fontWeight="bold"
onClick={devToolsDrawer.onToggle}
bg={`${env.NEXT_PUBLIC_ENV_COLOR_SCHEME}.400`}
color={`${env.NEXT_PUBLIC_ENV_COLOR_SCHEME}.900`}
px="1"
borderBottomStartRadius="sm"
borderBottomEndRadius="sm"
textTransform="uppercase"
display="flex"
alignItems="center"
gap="1"
>
<LuPanelLeftOpen />
Dev helper
</Text>
<DevToolsDrawer disclosure={devToolsDrawer} />
</>
);
};
72 changes: 72 additions & 0 deletions src/features/devtools/DevToolsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use client';

import {
Checkbox,
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerHeader,
FormControl,
FormLabel,
Radio,
RadioGroup,
Stack,
Wrap,
useColorMode,
useDisclosure,
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';

import { AVAILABLE_LANGUAGES } from '@/lib/i18n/constants';

export const DevToolsDrawer = ({
disclosure,
}: {
disclosure: ReturnType<typeof useDisclosure>;
}) => {
const { colorMode, setColorMode } = useColorMode();

const { t, i18n } = useTranslation(['common']);

return (
<Drawer
onClose={disclosure.onClose}
isOpen={disclosure.isOpen}
size="xs"
placement="left"
>
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Development panel</DrawerHeader>
<DrawerBody>
<Stack spacing={6}>
<Checkbox
isChecked={colorMode === 'dark'}
onChange={(e) =>
setColorMode(e.target.checked ? 'dark' : 'light')
}
>
Dark mode
</Checkbox>
<FormControl>
<FormLabel>Language</FormLabel>
<RadioGroup
value={i18n.language}
onChange={(newValue) => i18n.changeLanguage(newValue)}
>
<Wrap spacing={4}>
{AVAILABLE_LANGUAGES.map((language) => (
<Radio key={language.key} value={language.key}>
{t(`common:languages.${language.key}`)}
</Radio>
))}
</Wrap>
</RadioGroup>
</FormControl>
Comment on lines +44 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we can extract these components into external files; we can see them as a lib of standalone dev components we can add / remove based on project needs :) And I think it will make the drawer more readable in long term, even if it's only development tools

I don't know if it is too much or not, but I prefer to see something like this:

<DrawerBody>
  <Stack spacing={6}>
    <DarkModeDevTool />
    <LanguageSwitcherDevTool />
  </Stack>
</DrawerBody>

instead of adding everything inside the drawer; So if I add a new tool for managing tanstack query (toggle dev tools, delete cache, etc...) I can just add and It Just Works™ :p

I also know that I can update only DarkModeDevTool instead of finding related code inside the drawer for future updates :)

I think you get the idea :p

</Stack>
</DrawerBody>
</DrawerContent>
</Drawer>
);
};
43 changes: 0 additions & 43 deletions src/features/devtools/EnvHint.tsx

This file was deleted.

Loading