Skip to content

Commit

Permalink
feat: Create set username page (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
hamedtkd authored Jan 8, 2025
1 parent 2a57ed5 commit 28da27b
Show file tree
Hide file tree
Showing 17 changed files with 380 additions and 50 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

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

Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@ import { Textfield } from "@/components/ui/Textfield";
const CheckAvailabilityForm = () => {
const { handleSubmit, register, errors } = useCheckAvailability();
return (
<form
onSubmit={handleSubmit}
className=" space-y-6 max-w-[575px] mx-auto md:px-0 px-4 mt-[50px]"
>
<form onSubmit={handleSubmit} className=" space-y-6 ">
<div className="space-y-2">
<div className="flex items-center gap-4 animate-fade-up animate-delay-300">
<Textfield
className="flex-1"
className="w-full flex-1"
{...register("username")}
type="text"
placeholder="username"
Expand Down
4 changes: 2 additions & 2 deletions src/components/pages/Availability/CheckAvailabity/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import CheckAvailabilityForm from "./CheckAvailabilityForm";

const CheckAvailability = () => {
return (
<section>
<section className="space-y-[50px]">
<h1 className="gradient-text text text-center font-bold text-[80px] uppercase max-w-[873px] mx-auto leading-[56px] animate-fade-up ">
Give a better nostr id for yourself
</h1>
<div>
<div className="max-w-[575px] mx-auto ">
<CheckAvailabilityForm />
</div>
</section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ const useCheckAvailability = () => {
const onSubmit = async (data: { username: string }) => {
try {
setLoading(true);
await usernameService.checkAvailability(data.username);
navigate("");
const res = await usernameService.checkAvailability(data.username);
navigate(
`/set-username?username${data.username}&status=${res.data.data}`,
);
} catch (error) {
console.log(error);
navigate("");
// TODO : add toast
} finally {
setLoading(false);
}
Expand Down
30 changes: 30 additions & 0 deletions src/components/pages/SetUserName/IsAvailableUserName.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Alert } from "@/components/ui/Alert";

const IsAvailableUserName = ({
username,
status,
isUsernameAvailable,
}: {
username: string | null;
status: string | null;
isUsernameAvailable: boolean;
}) => {
return (
<div className="flex flex-col items-center gap-[30px] mt-[153px]">
<div>
{status && (
<Alert variant={isUsernameAvailable ? "success" : "error"}>
{isUsernameAvailable
? "Congratulation! This NIP-05 is available"
: "Sorry, this name is already taken."}
</Alert>
)}
</div>
<h2 className="gradient-text text text-center font-bold text-[64px] animate-fade-up ">
{username}
</h2>
</div>
);
};

export default IsAvailableUserName;
45 changes: 45 additions & 0 deletions src/components/pages/SetUserName/NameSuggestions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Skeleton } from "@/components/ui/skeleton";
import Tag from "@/components/ui/Tag";
import { usernameService } from "@/services/api/username.service";
import { useEffect, useState } from "react";

const NameSuggestions = () => {
const [data, setData] = useState<string[]>();
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetch = async () => {
try {
setLoading(true);
const res = await usernameService.getSuggestions();
setData(res.data.data);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
fetch();
}, []);
return (
<div className="space-y-6">
<h4 className="gradient-text text-xl font-bold">
Name suggestions:
</h4>
{loading ? (
<div className="flex flex-wrap gap-2">
{Array.from({ length: 3 }).map((_, index) => (
<Skeleton className="h-[31px] w-[139px]" key={index} />
))}
</div>
) : data && data?.length > 0 ? (
<div className="flex flex-wrap gap-2">
{data?.map((tag, key) => <Tag key={key}>{tag}</Tag>)}
</div>
) : (
"No result found!"
)}
</div>
);
};

export default NameSuggestions;
36 changes: 36 additions & 0 deletions src/components/pages/SetUserName/SetUserNameForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Button } from "@/components/ui/Button";
import useCheckAvailability from "./useSetUserNameForm";
import { Textfield } from "@/components/ui/Textfield";

const SetUserNameForm = () => {
const { handleSubmit, register, errors } = useCheckAvailability();
return (
<form onSubmit={handleSubmit} className=" space-y-6 ">
<div className="space-y-2">
<Textfield
labelClasses="uppercase"
label="your npub:"
className="flex-1"
{...register("npub")}
type="text"
placeholder="npub..."
/>

{errors.npub && (
<p className="text-[#F6543E] text-sm font-roboto-mono animate-fade-right">
{errors.npub.message}
</p>
)}
</div>

<Button
className=" w-full animate-fade-up animate-delay-700 h-14 font-medium font-roboto-mono "
type="submit"
>
Pay
</Button>
</form>
);
};

export default SetUserNameForm;
58 changes: 58 additions & 0 deletions src/components/pages/SetUserName/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useSearchParams } from "react-router-dom";
import IsAvailableUserName from "./IsAvailableUserName";
import SetUserNameForm from "./SetUserNameForm";
import CheckAvailabilityForm from "../Availability/CheckAvailabity/CheckAvailabilityForm";
import { UsernameStatus } from "@/enums/usernameStatus";
import { cn } from "@/lib/utils";
import NameSuggestions from "./NameSuggestions";

const SetUserName = () => {
const [searchParams] = useSearchParams();
const username = searchParams.get("username");
const status = searchParams.get("status");
const isUsernameAvailable = Number(status) === UsernameStatus.AVAILABLE;

return (
<section
className={cn("container mx-auto py-[152px] ", {
"space-y-[111px]": isUsernameAvailable,
"space-y-[72px]": !isUsernameAvailable,
})}
>
<IsAvailableUserName
isUsernameAvailable={isUsernameAvailable}
username={username}
status={status}
/>
<div className="max-w-[645px] mx-auto space-y-6 ">
{isUsernameAvailable ? (
<>
<SetUserNameForm />
<div>
<p className=" text-sm font-roboto-mono text-[#80899F]">
Lifetime payment for{" "}
<span className="font-bankGothic text-base font-light">
{" "}
{username}{" "}
</span>{" "}
5000 sats{" "}
</p>
</div>
</>
) : (
<>
<NameSuggestions />
<div className="space-y-6">
<h4 className="gradient-text text-xl font-bold">
OR Type another name:
</h4>
<CheckAvailabilityForm />
</div>
</>
)}
</div>
</section>
);
};

export default SetUserName;
47 changes: 47 additions & 0 deletions src/components/pages/SetUserName/useSetUserNameForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as yup from "yup";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { usernameService } from "@/services/api/username.service";

const schema = yup
.object({
npub: yup.string().required("Username is required"),
})
.required();

const useCheckAvailability = () => {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});

const onSubmit = async (data: { npub: string }) => {
try {
setLoading(true);
await usernameService.checkAvailability(data.npub);
navigate("");
} catch (error) {
console.log(error);
navigate("");
} finally {
setLoading(false);
}
};

return {
register,
onSubmit,
errors,
handleSubmit: handleSubmit(onSubmit),
loading,
};
};

export default useCheckAvailability;
16 changes: 16 additions & 0 deletions src/components/ui/Alert/alertVariants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { cva } from "class-variance-authority";

export const alertVariants = cva(
" text-[18px] font-roboto-mono leading-[33.71px] rounded-[8px] px-2 h-[34px] flex justify-center items-center",
{
variants: {
variant: {
success: "text-[#30E0A1]",
error: " text-[#F6543E] bg-[#F6543E1A]",
},
},
defaultVariants: {
variant: "success",
},
},
);
28 changes: 28 additions & 0 deletions src/components/ui/Alert/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { cn } from "@/lib/utils";
import { alertVariants } from "./alertVariants";
import { VariantProps } from "class-variance-authority";
import React from "react";

export interface alertProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof alertVariants> {
asChild?: boolean;
}

const Alert = React.forwardRef<HTMLDivElement, alertProps>(
({ className, variant, children, ...props }, ref) => {
return (
<div
ref={ref}
className={cn(alertVariants({ variant }), className)}
{...props}
>
{children}
</div>
);
},
);

Alert.displayName = "Alert";

export { Alert };
26 changes: 26 additions & 0 deletions src/components/ui/Tag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { cn } from "@/lib/utils";
import React from "react";

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
asChild?: boolean;
}

const Tag = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, children, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(
"font-roboto-mono text-base py-2 px-[6px] flex items-center justify-center h-[31px] rounded-[6px] text-[#80899F] bg-[#282E4A]",
className,
)}
{...props}
>
{children}
</button>
);
},
);

export default Tag;
Loading

0 comments on commit 28da27b

Please sign in to comment.