Skip to content

Commit

Permalink
check username uniqueness
Browse files Browse the repository at this point in the history
  • Loading branch information
Hartaithan committed Apr 22, 2023
1 parent f704106 commit f17b7b0
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 12 deletions.
11 changes: 10 additions & 1 deletion package-lock.json

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

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"next": "13.2.4",
"psn-api": "^2.9.0",
"react": "18.2.0",
"react-dom": "18.2.0"
"react-dom": "18.2.0",
"tabler-icons-react": "^1.56.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.55.0",
Expand Down
100 changes: 90 additions & 10 deletions pages/signUp.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, forwardRef } from "react";
import { useState, forwardRef, useEffect } from "react";
import { type IPage } from "@/models/AppModel";
import { type IUser, type ISignUpBody } from "@/models/AuthModel";
import {
Expand All @@ -15,11 +15,17 @@ import {
Text,
TextInput,
Title,
Loader,
useMantineTheme,
Box,
} from "@mantine/core";
import { useForm, isEmail, hasLength, isNotEmpty } from "@mantine/form";
import API from "@/api/API";
import { locales } from "@/constants/locales";
import { type ILocale } from "@/models/LocaleModel";
import { useSupabaseClient } from "@supabase/auth-helpers-react";
import { useDebouncedValue } from "@mantine/hooks";
import { UserCheck, UserX } from "tabler-icons-react";

const API_URL = process.env.NEXT_PUBLIC_API_URL;

Expand All @@ -38,6 +44,16 @@ const useStyles = createStyles(({ spacing }) => ({
transform: "translateY(-50%)",
zIndex: 10,
},
username: {
position: "relative",
},
unique: {
position: "absolute",
top: 0,
right: 0,
fontSize: "0.875rem",
lineHeight: 1.55,
},
}));

const SelectStyles: SelectProps["styles"] = ({ spacing }) => ({
Expand All @@ -59,7 +75,11 @@ const SelectItem = forwardRef<HTMLDivElement, ILocale>(

const SignUpPage: IPage = () => {
const { classes } = useStyles();
const supabase = useSupabaseClient();
const { colors } = useMantineTheme();
const [user, setUser] = useState<IUser | null>(null);
const [isChecking, setChecking] = useState<boolean>(false);
const [isUnique, setUnique] = useState<boolean>(false);

const form = useForm<ISignUpBody>({
initialValues: {
Expand All @@ -71,6 +91,7 @@ const SignUpPage: IPage = () => {
},
validate: {
email: isEmail("Invalid email"),
username: isNotEmpty("Language is required"),
password: hasLength(
{ min: 6 },
"Password should include at least 6 characters"
Expand All @@ -80,6 +101,8 @@ const SignUpPage: IPage = () => {
},
});

const [debounced] = useDebouncedValue(form.values.username, 500);

const handleSubmit = (values: typeof form.values): void => {
if (API_URL === undefined) {
console.error("API_URL not found");
Expand All @@ -95,6 +118,24 @@ const SignUpPage: IPage = () => {
});
};

const checkUsernameUniqueness = async (): Promise<void> => {
const { error } = await supabase
.from("profiles")
.select("id")
.eq("username", form.values.username)
.single();
setUnique(error != null);
};

useEffect(() => {
if (debounced.length !== 0) {
setChecking(true);
checkUsernameUniqueness().finally(() => setChecking(false));
} else {
setUnique(false);
}
}, [debounced]); // eslint-disable-line

return (
<Flex w="100%" h="100%" direction="column" justify="center" align="center">
{user != null ? (
Expand Down Expand Up @@ -123,14 +164,48 @@ const SignUpPage: IPage = () => {
placeholder="Enter your email"
{...form.getInputProps("email")}
/>
<TextInput
required
type="text"
label="Username"
autoComplete="off"
placeholder="Enter your username"
{...form.getInputProps("username")}
/>
<Box className={classes.username}>
{form.values.username.length > 0 && !isChecking && (
<Text
className={classes.unique}
color={isUnique ? colors.green[8] : colors.red[8]}
>
{isUnique ? "Username is unique" : "Username already taken"}
</Text>
)}
{form.values.username.length > 0 && isChecking && (
<Text className={classes.unique} color={colors.dark[0]}>
Checking...
</Text>
)}
<TextInput
required
type="text"
label="Username"
autoComplete="off"
placeholder="Enter your username"
{...form.getInputProps("username")}
onChange={(e) => {
const value = e.target.value;
if (value.length > 0) {
setChecking(true);
} else {
setChecking(false);
setUnique(false);
}
form.setFieldValue("username", e.target.value);
}}
rightSection={
isChecking ? (
<Loader size="xs" />
) : isUnique ? (
<UserCheck size={20} color={colors.green[8]} />
) : (
<UserX size={20} color={colors.red[8]} />
)
}
/>
</Box>
<PasswordInput
required
type="password"
Expand Down Expand Up @@ -164,7 +239,12 @@ const SignUpPage: IPage = () => {
</div>
</Input.Wrapper>
</Stack>
<Button type="submit" mt="xl" fullWidth>
<Button
type="submit"
mt="xl"
disabled={!form.isValid() || isChecking || !isUnique}
fullWidth
>
Sign up!
</Button>
</form>
Expand Down

0 comments on commit f17b7b0

Please sign in to comment.