Skip to content

Commit

Permalink
feat: use nuqs package instead of custom code for storing state in url
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-dalmet committed Jan 5, 2024
1 parent cdd680e commit dbbf142
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 85 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"next": "14.0.4",
"nextjs-toploader": "1.6.4",
"nodemailer": "6.9.7",
"nuqs": "1.14.0",
"pino": "8.16.2",
"pino-pretty": "10.2.3",
"react": "18.2.0",
Expand Down
60 changes: 52 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 12 additions & 13 deletions src/features/account/AccountEmailForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Button, Flex, Stack } from '@chakra-ui/react';
import { Formiz, useForm, useFormFields } from '@formiz/core';
import { isEmail } from '@formiz/validations';
import { useSearchParams } from 'next/navigation';
import { parseAsString, useQueryStates } from 'nuqs';
import { useTranslation } from 'react-i18next';

import { ErrorPage } from '@/components/ErrorPage';
Expand All @@ -14,14 +15,17 @@ import {
EmailVerificationCodeModale,
SEARCH_PARAM_VERIFY_EMAIL,
} from '@/features/account/EmailVerificationCodeModal';
import { useSearchParamsUpdater } from '@/hooks/useSearchParamsUpdater';
import { trpc } from '@/lib/trpc/client';

export const AccountEmailForm = () => {
const { t } = useTranslation(['common', 'account']);
const searchParams = useSearchParams();
const verifyEmail = searchParams.get(SEARCH_PARAM_VERIFY_EMAIL);
const searchParamsUpdater = useSearchParamsUpdater();
const [searchParams, setSearchParams] = useQueryStates({
[SEARCH_PARAM_VERIFY_EMAIL]: parseAsString,
token: parseAsString,
verifyEmail: parseAsString,
});
const verifyEmail = searchParams[SEARCH_PARAM_VERIFY_EMAIL];

const account = trpc.account.get.useQuery(undefined, {
refetchOnReconnect: false,
refetchOnWindowFocus: false,
Expand All @@ -31,15 +35,10 @@ export const AccountEmailForm = () => {

const updateEmail = trpc.account.updateEmail.useMutation({
onSuccess: async ({ token }, { email }) => {
searchParamsUpdater(
{
[SEARCH_PARAM_VERIFY_EMAIL]: email,
token,
},
{
replace: true,
}
);
setSearchParams({
[SEARCH_PARAM_VERIFY_EMAIL]: email,
token,
});
},
onError: () => {
toastError({
Expand Down
30 changes: 14 additions & 16 deletions src/features/account/EmailVerificationCodeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,43 @@ import {
ModalOverlay,
} from '@chakra-ui/react';
import { Formiz, useForm } from '@formiz/core';
import { useSearchParams } from 'next/navigation';
import { parseAsInteger, parseAsString, useQueryStates } from 'nuqs';
import { useTranslation } from 'react-i18next';

import { useToastSuccess } from '@/components/Toast';
import {
VerificationCodeForm,
useOnVerificationCodeError,
} from '@/features/auth/VerificationCodeForm';
import { useSearchParamsUpdater } from '@/hooks/useSearchParamsUpdater';
import { trpc } from '@/lib/trpc/client';

export const SEARCH_PARAM_VERIFY_EMAIL = 'verify-email';

export const EmailVerificationCodeModale = () => {
const { t } = useTranslation(['account']);
const searchParams = useSearchParams();
const searchParamsUpdater = useSearchParamsUpdater();
const verifyEmail = searchParams.get(SEARCH_PARAM_VERIFY_EMAIL);
const token = searchParams.get('token');
const [searchParams, setSearchParams] = useQueryStates({
[SEARCH_PARAM_VERIFY_EMAIL]: parseAsString,
token: parseAsString,
attempts: parseAsInteger.withDefault(0),
verifyEmail: parseAsString,
});
const trpcUtils = trpc.useUtils();
const toastSuccess = useToastSuccess();

const onClose = () => {
trpcUtils.account.get.reset();
searchParamsUpdater(
{
[SEARCH_PARAM_VERIFY_EMAIL]: null,
token: null,
attempts: null,
},
{ replace: true }
);
setSearchParams({
[SEARCH_PARAM_VERIFY_EMAIL]: null,
token: null,
attempts: null,
});
};

const form = useForm<{ code: string }>({
onValidSubmit: (values) => {
updateEmailValidate.mutate({
code: values.code,
token: token ?? '',
token: searchParams.token ?? '',
});
},
});
Expand All @@ -69,7 +67,7 @@ export const EmailVerificationCodeModale = () => {
<ModalBody>
<Formiz connect={form} autoForm>
<VerificationCodeForm
email={verifyEmail ?? ''}
email={searchParams.verifyEmail ?? ''}
isLoading={updateEmailValidate.isLoading}
/>
</Formiz>
Expand Down
16 changes: 6 additions & 10 deletions src/features/auth/VerificationCodeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FormContext, useFormContext } from '@formiz/core';
import { useQueryClient } from '@tanstack/react-query';
import { TRPCClientErrorLike } from '@trpc/client';
import { useRouter, useSearchParams } from 'next/navigation';
import { parseAsInteger, useQueryState } from 'nuqs';
import { Trans, useTranslation } from 'react-i18next';

import { FieldPinInput } from '@/components/FieldPinInput';
Expand All @@ -11,7 +12,6 @@ import {
getValidationRetryDelayInSeconds,
} from '@/features/auth/utils';
import { DevCodeHint } from '@/features/devtools/DevCodeHint';
import { useSearchParamsUpdater } from '@/hooks/useSearchParamsUpdater';
import { trpc } from '@/lib/trpc/client';
import { AppRouter } from '@/lib/trpc/types';

Expand Down Expand Up @@ -99,20 +99,16 @@ export const useOnVerificationCodeSuccess = ({

export const useOnVerificationCodeError = ({ form }: { form: FormContext }) => {
const { t } = useTranslation(['auth']);
const searchParams = useSearchParams();
const searchParamsUpdater = useSearchParamsUpdater();
const [attempts, setAttemps] = useQueryState(
'attemps',
parseAsInteger.withDefault(0)
);

return async (error: TRPCClientErrorLike<AppRouter>) => {
if (error.data?.code === 'UNAUTHORIZED') {
const attempts = parseInt(searchParams.get('attempts') ?? '0', 10);
const seconds = getValidationRetryDelayInSeconds(attempts);

searchParamsUpdater(
{
attempts: (attempts + 1).toString(),
},
{ replace: true }
);
setAttemps((s) => s + 1);

await new Promise((r) => {
setTimeout(r, seconds * 1_000);
Expand Down
9 changes: 3 additions & 6 deletions src/features/repositories/PageAdminRepositories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Text,
} from '@chakra-ui/react';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import { useQueryState } from 'nuqs';
import { Trans, useTranslation } from 'react-i18next';
import { LuBookMarked, LuPlus } from 'react-icons/lu';

Expand All @@ -33,14 +33,11 @@ import {
} from '@/features/admin/AdminLayoutPage';
import { ADMIN_PATH } from '@/features/admin/constants';
import { AdminRepositoryActions } from '@/features/repositories/AdminRepositoryActions';
import { useSearchParamsUpdater } from '@/hooks/useSearchParamsUpdater';
import { trpc } from '@/lib/trpc/client';

export default function PageAdminRepositories() {
const { t } = useTranslation(['repositories']);
const searchParams = useSearchParams();
const searchParamsUpdater = useSearchParamsUpdater();
const searchTerm = searchParams.get('s') ?? '';
const [searchTerm, setSearchTerm] = useQueryState('s', { defaultValue: '' });

const repositories = trpc.repositories.getAll.useInfiniteQuery(
{ searchTerm },
Expand All @@ -67,7 +64,7 @@ export default function PageAdminRepositories() {
<SearchInput
value={searchTerm}
size="sm"
onChange={(value) => searchParamsUpdater({ s: value || null })}
onChange={(value) => setSearchTerm(value || null)}
maxW={{ base: 'none', md: '20rem' }}
/>
</Flex>
Expand Down
10 changes: 4 additions & 6 deletions src/features/users/PageAdminUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
Text,
} from '@chakra-ui/react';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import { useQueryState } from 'nuqs';
import { Trans, useTranslation } from 'react-i18next';
import { LuPlus } from 'react-icons/lu';

Expand All @@ -36,16 +36,14 @@ import {
import { ADMIN_PATH } from '@/features/admin/constants';
import { AdminNav } from '@/features/management/ManagementNav';
import { UserStatus } from '@/features/users/UserStatus';
import { useSearchParamsUpdater } from '@/hooks/useSearchParamsUpdater';
import { trpc } from '@/lib/trpc/client';

import { AdminUserActions } from './AdminUserActions';

export default function PageAdminUsers() {
const { t } = useTranslation(['users']);
const searchParams = useSearchParams();
const searchParamsUpdater = useSearchParamsUpdater();
const searchTerm = searchParams.get('s') ?? '';
const [searchTerm, setSearchTerm] = useQueryState('s', { defaultValue: '' });

const account = trpc.account.get.useQuery();

const users = trpc.users.getAll.useInfiniteQuery(
Expand Down Expand Up @@ -73,7 +71,7 @@ export default function PageAdminUsers() {
<SearchInput
size="sm"
value={searchTerm}
onChange={(value) => searchParamsUpdater({ s: value || null })}
onChange={(value) => setSearchTerm(value || null)}
maxW={{ base: 'none', md: '20rem' }}
/>
</Flex>
Expand Down
Loading

0 comments on commit dbbf142

Please sign in to comment.