Skip to content

Commit

Permalink
Merge pull request #848 from vijaysingh2219/main
Browse files Browse the repository at this point in the history
feat: Add email domain suggestions to signin component
  • Loading branch information
devsargam authored Sep 1, 2024
2 parents f207a55 + b2727dc commit ff19b43
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 9 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tooltip": "^1.0.7",
"@types/bcrypt": "^5.0.2",
Expand Down
108 changes: 99 additions & 9 deletions src/components/Signin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,33 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Separator } from '@/components/ui/separator';
import { signIn } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import React, { useRef, useState } from 'react';

import { toast } from 'sonner';

const emailDomains = [
'gmail.com',
'yahoo.com',
'outlook.com',
'icloud.com',
'hotmail.com',
'rediffmail.com',
];

const Signin = () => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const [checkingPassword, setCheckingPassword] = useState(false);
const [requiredError, setRequiredError] = useState({
emailReq: false,
passReq: false,
});
const [suggestedDomains, setSuggestedDomains] =
useState<string[]>(emailDomains);
const [focusedIndex, setFocusedIndex] = useState(-1);
const passwordRef = useRef<HTMLInputElement>(null);
const suggestionRefs = useRef<HTMLLIElement[]>([]);

function togglePasswordVisibility() {
setIsPasswordVisible((prevState: any) => !prevState);
Expand All @@ -23,6 +38,58 @@ const Signin = () => {
const email = useRef('');
const password = useRef('');

const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
email.current = value;

setFocusedIndex(0);
setRequiredError((prevState) => ({
...prevState,
emailReq: false,
}));

if (!value.includes('@')) {
setSuggestedDomains(emailDomains);
return;
}

const [, currentDomain] = value.split('@');
// Check for exact matches and filter for partial matches
const exactMatch = emailDomains.find((domain) => domain === currentDomain);
if (exactMatch) {
setSuggestedDomains([]);
return;
}

const matchingDomains = emailDomains.filter((domain) =>
domain.startsWith(currentDomain),
);
setSuggestedDomains(matchingDomains);
};

const handleSuggestionClick = (domain: string) => {
const [username] = email.current.split('@');
const newEmail = `${username}@${domain}`;
email.current = newEmail;
passwordRef.current?.focus();
setSuggestedDomains([]);
};

// Handle keyboard events for navigating and selecting suggestions
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && focusedIndex >= 0 && suggestedDomains.length > 0) {
handleSuggestionClick(suggestedDomains[focusedIndex]);
} else if (e.key === 'ArrowDown') {
e.preventDefault();
setFocusedIndex((prevIndex) =>
Math.min(prevIndex + 1, suggestedDomains.length - 1),
);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setFocusedIndex((prevIndex) => Math.max(prevIndex - 1, 0));
}
};

const handleSubmit = async (e?: React.FormEvent<HTMLButtonElement>) => {
const loadId = toast.loading('Signing in...');
if (e) {
Expand Down Expand Up @@ -61,21 +128,43 @@ const Signin = () => {
</CardHeader>
<CardContent>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col gap-4">
<div className="relative flex flex-col gap-4">
<Label htmlFor="email">Email</Label>
<Input
className="px-2"
name="email"
id="email"
placeholder="[email protected]"
onChange={(e) => {
setRequiredError((prevState) => ({
...prevState,
emailReq: false,
}));
email.current = e.target.value;
}}
value={email.current}
onChange={handleEmailChange}
onKeyDown={handleKeyDown}
/>
{email.current && suggestedDomains.length > 0 && (
<ul
className={`absolute top-20 z-50 max-h-96 w-full min-w-[8rem] overflow-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2`}
>
{suggestedDomains.map((domain: string, index: number) => (
<>
<li
key={domain}
value={domain}
ref={(listItem) =>
(suggestionRefs.current[index] = listItem!)
}
onClick={() => handleSuggestionClick(domain)}
className={`relative flex w-full cursor-default select-none items-center rounded-sm p-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 ${
focusedIndex === index
? 'bg-primary-foreground font-medium'
: ''
}`}
>
{email.current.split('@')[0]}@{domain}
</li>
{index < suggestedDomains.length - 1 && <Separator />}
</>
))}
</ul>
)}
{requiredError.emailReq && (
<span className="text-red-500">Email is required</span>
)}
Expand All @@ -89,6 +178,7 @@ const Signin = () => {
type={isPasswordVisible ? 'text' : 'password'}
id="password"
placeholder="••••••••"
ref={passwordRef}
onChange={(e) => {
setRequiredError((prevState) => ({
...prevState,
Expand Down
31 changes: 31 additions & 0 deletions src/components/ui/separator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use client';

import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';

import { cn } from '@/lib/utils';

const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = 'horizontal', decorative = true, ...props },
ref,
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
'shrink-0 bg-border',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className,
)}
{...props}
/>
),
);
Separator.displayName = SeparatorPrimitive.Root.displayName;

export { Separator };

0 comments on commit ff19b43

Please sign in to comment.