Skip to content

Commit

Permalink
Merge pull request #238 from Demandtech/feat/HNG-21-otp-verification
Browse files Browse the repository at this point in the history
Feat/hng 21 otp verification
  • Loading branch information
SirhmVFX authored Jul 20, 2024
2 parents 3a82f7b + 41c55bb commit a91ab12
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 12 deletions.
24 changes: 12 additions & 12 deletions app/components/BlogCards.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";

interface BlogCardProps {
interface BlogCardProperties {
title: string;
description: string;
date: string;
Expand All @@ -12,7 +12,7 @@ interface BlogCardProps {
link: string;
}

const BlogCard: React.FC<BlogCardProps> = ({
const BlogCard: React.FC<BlogCardProperties> = ({
title,
description,
date,
Expand All @@ -24,31 +24,31 @@ const BlogCard: React.FC<BlogCardProps> = ({
link,
}) => {
return (
<div className="max-w-sm lg:max-w-full lg:flex lg:flex-row rounded overflow-hidden shadow-lg m-4">
<img className="w-full lg:w-1/3 lg:order-2" src={blogImage} alt={title} />
<div className="p-4 lg:w-2/3 lg:order-1">
<div className="flex items-center mb-2">
<span className="inline-block w-3 h-3 rounded-full bg-gray-400 mr-2"></span>
<div className="m-4 max-w-sm overflow-hidden rounded shadow-lg lg:flex lg:max-w-full lg:flex-row">
<img className="w-full lg:order-2 lg:w-1/3" src={blogImage} alt={title} />
<div className="p-4 lg:order-1 lg:w-2/3">
<div className="mb-2 flex items-center">
<span className="mr-2 inline-block h-3 w-3 rounded-full bg-gray-400"></span>
<span className="text-sm font-semibold text-gray-700">{tag}</span>
</div>
<div>
<a href={link} className="text-black hover:text-blue-800">
<h3 className="font-bold text-xl mb-2">{title}</h3>
<h3 className="mb-2 text-xl font-bold">{title}</h3>
</a>
<p className="text-gray-700 text-base mb-4">{description}</p>
<p className="mb-4 text-base text-gray-700">{description}</p>
</div>
<div className="flex justify-between text-gray-500 text-sm mb-4">
<div className="mb-4 flex justify-between text-sm text-gray-500">
<span>{date}</span>
<span>{timeOfReading} mins Read</span>
</div>
<div className="flex items-center">
<img
className="w-10 h-10 rounded-full mr-4"
className="mr-4 h-10 w-10 rounded-full"
src={authorProfilePicture}
alt={authorName}
/>
<div className="text-sm">
<p className="text-gray-900 leading-none">{authorName}</p>
<p className="leading-none text-gray-900">{authorName}</p>
</div>
</div>
</div>
Expand Down
143 changes: 143 additions & 0 deletions app/components/ui/otp/OtpAuth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Link } from "@remix-run/react";
import React, { ChangeEvent, useRef, useState } from "react";
import { twMerge } from "tailwind-merge";

import { Input, OtpAuthProperties } from "~/types/otpauth";
import { Button } from "../button";

const OtpAuth: React.FC<OtpAuthProperties> = ({
isModalOpen = false,
inputs = [],
setIsModalOpen,
handleSubmit,
}) => {
const [inputValues, setInputValues] = useState<Input[]>(inputs);
const inputReferences = useRef<(HTMLInputElement | null)[]>([]);

const handleChange =
(index: number) => (event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
if (/^\d?$/.test(value)) {
const newInputs = [...inputValues];
newInputs[index].value = value;

setInputValues(newInputs);
if (value && index < inputReferences.current.length - 1) {
inputReferences.current[index + 1]?.focus();
}
}
};

const handleClose: React.MouseEventHandler<HTMLDivElement> = () => {
if (setIsModalOpen) {
setIsModalOpen(false);
}
};

const handleSubmitForm: React.MouseEventHandler<HTMLButtonElement> = () => {
if (handleSubmit) {
handleSubmit(inputValues);
}
};

const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
if (event.key === "Escape" && setIsModalOpen) {
setIsModalOpen(false);
}
};

const modalClass = twMerge(
"animate-in fade-in zoom-in duration-300",
isModalOpen ? "visible opacity-100" : "invisible opacity-0",
);

const modalContentClass = twMerge(
"fixed top-[50%] left-[50%] max-h-[85vh] delay-150 animate-in fade-in zoom-in duration-300 w-[95%] sm:max-w-[470px] translate-x-[-50%] translate-y-[-50%] rounded-[6px] bg-white p-[25px] shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none ",
isModalOpen ? "opacity-100 scale-100" : "opacity-0 scale-95",
);

return (
<main className={modalClass}>
<div>
<div
tabIndex={0}
onKeyDown={handleKeyDown}
role="button"
onClick={handleClose}
className="bg-blackA6 fixed inset-0 backdrop-brightness-75 animate-in animate-out"
/>
<div className={modalContentClass}>
<h3 className="text-mauve12 mb-4 text-center text-3xl font-semibold text-primary">
Sign up
</h3>
<div className="mb-8 text-center text-sm text-[#64748B]">
<p className="mb-1">Choose your sign-up method:</p>
<ul className="list-inside list-disc space-y-1">
<li>Use the temporary sign-in code sent to your mail or</li>
<li>Continue with email and password</li>
</ul>
</div>

<p className="mb-8 text-center text-sm text-primary">
Please paste (or type) your 6-digit code:{" "}
</p>

<div className="mb-8 flex justify-between">
{inputValues.map((input, index) => {
return (
<input
ref={(element) => (inputReferences.current[index] = element)}
name={input.name}
value={input.value}
onChange={handleChange(index)}
key={index}
className={`fomt-medium inline-flex min-h-[40px] min-w-[40px] rounded-md text-center text-lg text-primary outline-none sm:h-[60px] sm:w-[60px] ${
input.value ? "border-[#EF4444]" : "border-border"
} border`}
id={input.name}
inputMode="numeric"
pattern="[0-9]*"
tabIndex={index}
/>
);
})}
</div>
{inputValues.every((input) => input.value.length === 1) && (
<div className="mb-8">
<Button
size={"lg"}
onClick={handleSubmitForm}
className="w-full rounded-sm bg-[#F97316] text-white"
>
Continue
</Button>
</div>
)}
<div className="max-w-sm text-center text-[#64748B]">
<div className="mb-3">
<p className="text-xs leading-5">
Would you rather use email and password?
</p>
<Link to={"#"} className="text-xs font-semibold text-[#F97316]">
Continue with email and password
</Link>
</div>
<p className="text-[10px] leading-5">
We would process your data as set forth in our
<Link to="#" className="font-semibold text-[#F97316]">
{" "}
Terms of Use, Privacy Policy
</Link>
and{" "}
<Link to="#" className="font-semibold text-[#F97316]">
Data Processing Agreement
</Link>
</p>
</div>
</div>
</div>
</main>
);
};

export default OtpAuth;
25 changes: 25 additions & 0 deletions app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { MetaFunction } from "@remix-run/node";
import { useState } from "react";

import { Button } from "~/components/ui/button";
import CardPlatform from "~/components/ui/card/card-platform";
import OtpAuth from "~/components/ui/otp/OtpAuth";
import { Input } from "~/types/otpauth";

export const meta: MetaFunction = () => {
return [
Expand All @@ -10,7 +13,13 @@ export const meta: MetaFunction = () => {
];
};

const handleSubmit = (values: Input[]) => {
console.log({ values });
};

export default function Index() {
const [openModal, setOpenModal] = useState(false);

return (
<div className="p-4 font-sans">
<h1 className="text-3xl">Welcome to Remix</h1>
Expand All @@ -36,6 +45,9 @@ export default function Index() {
</a>
</li>
<Button>Hello</Button>
<div>
<Button onClick={() => setOpenModal(true)}>Open OTP modal</Button>
</div>
<div className="p-2">
<CardPlatform
logo="/images/g-drive-icon.svg"
Expand All @@ -55,6 +67,19 @@ export default function Index() {
</a>
</li>
</ul>
<OtpAuth
isModalOpen={openModal}
setIsModalOpen={() => setOpenModal(!openModal)}
inputs={[
{ name: "input1", value: "" },
{ name: "input1", value: "" },
{ name: "input1", value: "" },
{ name: "input1", value: "" },
{ name: "input1", value: "" },
{ name: "input1", value: "" },
]}
handleSubmit={handleSubmit}
/>
</div>
);
}
11 changes: 11 additions & 0 deletions app/types/otpauth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type Input = {
name: string;
value: string;
};

export type OtpAuthProperties = {
setIsModalOpen?: (isOpen: boolean) => void;
handleSubmit?: (values: Input[]) => void;
isModalOpen?: boolean;
inputs?: Input[];
};

0 comments on commit a91ab12

Please sign in to comment.