Skip to content

Commit

Permalink
[Password] Fix server error on load and add generate button (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
inssein authored Jan 9, 2024
1 parent 64189a7 commit 653cca9
Showing 1 changed file with 74 additions and 21 deletions.
95 changes: 74 additions & 21 deletions app/routes/password-generator.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { useState } from "react";
import Box, { BoxContent, BoxTitle } from "~/components/box";
import {
type ChangeEvent,
useCallback,
useLayoutEffect,
useReducer,
useState,
} from "react";
import Box, { BoxButtons, BoxContent, BoxTitle } from "~/components/box";
import ContentWrapper from "~/components/content-wrapper";
import { ClientOnly } from "~/components/client-only";
import { metaHelper } from "~/utils/meta";
import { utilities } from "~/utilities";
import Slider from "~/components/slider";
import Checkbox from "~/components/checkbox";
import ReadOnlyTextArea from "~/components/read-only-textarea";
import Copy from "~/components/copy";
import Button from "~/components/button";
import NumberInput from "~/components/number-input";

export const meta = metaHelper(
utilities.password.name,
Expand Down Expand Up @@ -82,18 +89,47 @@ function generatePassword(
}

export default function PasswordGenerator() {
const [number, setNumber] = useState(1);
const [characters, setCharacters] = useState(20);
const [useUpperCase, setUseUpperCase] = useState(true);
const [useLowerCase, setLowerUpperCase] = useState(true);
const [useDigits, setUseDigits] = useState(true);
const [useSpecial, setUseSpecial] = useState(true);
const output = generatePassword(
const [generatedPassword, setGeneratedPassword] = useState("");

// hack: we don't have a way to re-render inside a function component without something changing
const [forceUpdate, setForceUpdate] = useReducer((x) => x + 1, 0);

const onChangeNumber = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setNumber(Math.min(Math.max(parseInt(e.target.value, 10), 1), 500));
},
[setNumber],
);

// we need to use a layout effect here because `generatePassword` calls upon `window.crypto`, which is not available
// when rendering on the server side (node).
useLayoutEffect(() => {
setGeneratedPassword(
Array.from(Array(number), () =>
generatePassword(
characters,
useUpperCase,
useLowerCase,
useDigits,
useSpecial,
),
).join("\n"),
);
}, [
characters,
useUpperCase,
useLowerCase,
useDigits,
useLowerCase,
useSpecial,
);
useUpperCase,
number,
forceUpdate,
]);

return (
<ContentWrapper>
Expand All @@ -102,7 +138,7 @@ export default function PasswordGenerator() {
<Box>
<BoxTitle title="Settings" />

<BoxContent isLast={true} className="px-3 py-2 flex flex-col gap-y-2">
<BoxContent isLast={false} className="px-3 py-2 flex flex-col gap-y-2">
<Slider
id="characters"
label={`Characters (${characters})`}
Expand Down Expand Up @@ -140,22 +176,39 @@ export default function PasswordGenerator() {
onChange={(e) => setUseSpecial(e.target.checked)}
/>
</BoxContent>

<BoxButtons>
<div className="flex w-full justify-end gap-x-2">
<Button onClick={setForceUpdate} label="Generate" />
</div>
</BoxButtons>
</Box>

<ClientOnly>
{() => (
<Box className="mt-6">
<BoxTitle title="Output">
<div>
<Copy content={output} />
<Box className="mt-6">
<BoxTitle title="Output">
<div className="flex flex-wrap items-center divide-gray-200 sm:divide-x dark:divide-gray-600">
<div className="flex items-center space-x-1 sm:px-4">
<div className="sm:px text-center">
<NumberInput
id="length"
type="number"
min={1}
max={500}
value={number}
onChange={onChangeNumber}
/>
</div>
</BoxTitle>
<BoxContent isLast={true}>
<ReadOnlyTextArea value={output} />
</BoxContent>
</Box>
)}
</ClientOnly>
</div>

<div className="flex flex-wrap items-center space-x-1 sm:pl-4">
<Copy content={generatedPassword} />
</div>
</div>
</BoxTitle>
<BoxContent isLast={true}>
<ReadOnlyTextArea value={generatedPassword} />
</BoxContent>
</Box>
</ContentWrapper>
);
}

0 comments on commit 653cca9

Please sign in to comment.