Skip to content

Commit

Permalink
feat: Update verification timeout, enhance MainCard layout, and add A…
Browse files Browse the repository at this point in the history
…uthError tests
  • Loading branch information
kunaldevxxx committed Dec 21, 2024
1 parent 73b3692 commit baf4a0c
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 46 deletions.
Binary file added public/Login.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/LogoSignup.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/Logo_Sign.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/app/auth/components/AuthButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ const AuthButtons: React.FC = () => {
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center">
<span className="bg-white px-4 text-gray-500 text-sm">or</span>
<span className="bg-white rounded-2xl px-4 text-gray-500 text-sm">or</span>
</div>
</div>

<Link
href="/api/auth/login"
className="w-full flex items-center justify-center px-6 py-3 bg-white-50 text-black-600 rounded-xl hover:bg-gray-100 transition-all shadow-md group focus:ring-2 focus:ring-gray-300 focus:outline-none"
className="w-full flex items-center justify-center bg-white px-6 py-3 bg-white-50 text-black-600 rounded-xl hover:bg-gray-100 transition-all shadow-md group focus:ring-2 focus:ring-gray-300 focus:outline-none"
>
<Lock className="mr-3 group-hover:animate-pulse" />
Login
Expand Down
50 changes: 50 additions & 0 deletions src/app/auth/error/__test__/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import { useSearchParams } from 'next/navigation'
import { Suspense } from 'react'
import AuthError from '../page'

// Mock useSearchParams from next/navigation
jest.mock('next/navigation', () => ({
useSearchParams: jest.fn(),
}))

describe('AuthError Component', () => {
it('renders with a default error message when no message is provided', () => {
(useSearchParams as jest.Mock).mockReturnValue({
get: jest.fn().mockReturnValue(null),
})

render(<AuthError />)

expect(screen.getByText('Authentication Error')).toBeInTheDocument()
expect(screen.getByText('An unknown error occurred')).toBeInTheDocument()
expect(screen.getByRole('link', { name: /Return to Sign In/i })).toBeInTheDocument()
})

it('renders with a specific error message from search params', () => {
(useSearchParams as jest.Mock).mockReturnValue({
get: jest.fn().mockReturnValue(encodeURIComponent('Invalid credentials')),
})

render(<AuthError />)

expect(screen.getByText('Authentication Error')).toBeInTheDocument()
expect(screen.getByText('Invalid credentials')).toBeInTheDocument()
expect(screen.getByRole('link', { name: /Return to Sign In/i })).toBeInTheDocument()
})

it('renders the fallback loading state', () => {
(useSearchParams as jest.Mock).mockImplementation(() => {
throw new Promise(() => {}); // Never resolves, forcing Suspense
});

const { container } = render(
<Suspense fallback={<div>Loading...</div>}>
<AuthError />
</Suspense>
)

expect(container).toHaveTextContent('Loading...')
})
})
14 changes: 7 additions & 7 deletions src/app/auth/error/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ function AuthErrorComponent() {
const errorMessage = searchParams?.get('message') || 'An unknown error occurred'

return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full p-6 bg-white rounded-lg shadow-lg">
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<div className="max-w-md w-full p-6 bg-white rounded-2xl shadow-2xl border border-blue-500">
<div className="text-center">
<h1 className="text-red-600 text-4xl font-bold mb-4">Authentication Error</h1>
<div className="bg-red-50 border border-red-200 rounded-md p-4 mb-4">
<p className="text-red-700">{decodeURIComponent(errorMessage)}</p>
<h1 className="text-black text-4xl font-extrabold mb-4">Authentication Error</h1>
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-4">
<p className="text-black">{decodeURIComponent(errorMessage)}</p>
</div>
<Link
href="/api/auth/login"
className="inline-block bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors"
className="inline-block bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-full transition-colors"
>
Return to Sign In
</Link>
Expand All @@ -34,4 +34,4 @@ export default function AuthError() {
<AuthErrorComponent />
</Suspense>
)
}
}
69 changes: 49 additions & 20 deletions src/app/auth/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,62 @@ import Badge from '../../components/Badge';
import TermsAndPrivacy from '../../components/TermsAndPrivacy';
import MainCard from '../../components/MainCard';
import { CheckCircle, Shield } from 'lucide-react';
import ImageSection from "./components/ImageSection";
import TextSection from "./components/TextSection";
import AuthButtons from './components/AuthButtons';
import Image from 'next/image';

const Auth = () => {
const AuthPage = () => {
return (
<main className="flex items-center justify-center min-h-screen bg-gradient-to-br from-blue-50 to-white p-8 font-inter">
<div className="min-h-screen flex flex-col lg:flex-row bg-gray-100">
<div className="w-full lg:w-1/2 relative overflow-hidden min-h-[300px] lg:min-h-screen">
<div
className="absolute inset-0"
style={{
backgroundImage: `url('/Login.jpeg')`,
backgroundSize: 'cover',
backgroundPosition: 'center'
}}
>
<div className="absolute inset-0 bg-black/5" />
</div>
<div className="relative h-full flex flex-col justify-between p-4 sm:p-6 lg:p-8">
<h2 className="text-2xl sm:text-3xl font-medium text-black">Creating Memories</h2>
<div className="text-black space-y-2 mb-10 lg:mb-20">
<h2 className="text-2xl sm:text-3xl font-medium">Capturing Moments,</h2>
<h2 className="text-2xl sm:text-3xl font-medium">Creating Memories</h2>
<div className="flex gap-2 mt-4 lg:mt-6">
<div className="w-2 h-2 rounded-full bg-gray-400"></div>
<div className="w-2 h-2 rounded-full bg-gray-400"></div>
<div className="w-2 h-2 rounded-full bg-black"></div>
</div>
</div>
</div>
</div>
<div className="w-full lg:w-1/2 bg-[#f8f8f8] flex content-center items-center border-l-rounded justify-center p-4 sm:p-6 lg:p-8">
<MainCard>
<div className="p-8 lg:p-12 flex flex-col items-center justify-center bg-gray-50 border-r border-gray-300 relative">
<div className="flex space-x-2 items-center pb-4">
<Badge icon={CheckCircle} text="Secure" bgColor="bg-green-100" textColor="text-green-600" />
<Badge icon={Shield} text="Verified" bgColor="bg-blue-100" textColor="text-blue-600" />
</div>
<ImageSection />
<TextSection />
<div className="w-full max-w-md space-y-4 lg:space-y-6">
<div className="flex justify-center mb-6">
<Image src="/Logo_Sign.svg" alt="SafeDep Logo" width={128} height={128} />
</div>
<div className="p-8 lg:p-12 flex flex-col justify-center space-y-8">
<div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Get Started</h1>
<p className="text-gray-600">Register for SafeDep Cloud to get API access.</p>
</div>
<AuthButtons />
<TermsAndPrivacy />
<div className="flex items-center justify-center space-x-2 mb-4 lg:mb-6">
<Badge icon={CheckCircle} text="Secure" bgColor="bg-green-100" textColor="text-green-600" />
<Badge icon={Shield} text="Verified" bgColor="bg-blue-100" textColor="text-blue-600" />
</div>

<div className="text-center">
<h1 className="text-xl sm:text-2xl font-bold text-black mb-3 lg:mb-4">Welcome to SafeDep</h1>
<p className="text-black mb-6 lg:mb-8 text-sm sm:text-base">
Get access to SafeDep Cloud APIs to integrate open-source component analysis and risk assessment into your CI/CD pipeline or custom workflow.
</p>
</div>

<AuthButtons />

<TermsAndPrivacy />
</div>
</MainCard>
</main>
</div>
</div>
);
};

export default Auth;
export default AuthPage;
65 changes: 65 additions & 0 deletions src/app/error/verify-email/__test__/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Page from '../page';

describe('Page Component', () => {
it('renders the main elements correctly', () => {
render(<Page />);

// Check for the title
expect(screen.getByText('Verify your email')).toBeInTheDocument();

// Check for the description
expect(screen.getByText(/We ve sent you a verification link/i)).toBeInTheDocument();

// Check for the steps
const steps = [
'Open your email inbox',
'Click the verification link we sent you',
'Return here to continue',
];
steps.forEach((step) => {
expect(screen.getByText(step)).toBeInTheDocument();
});

// Check for the buttons
expect(screen.getByRole('button', { name: /Continue to Login/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Return to Home/i })).toBeInTheDocument();

// Check for the help text
expect(screen.getByText(/Didnt receive the email/i)).toBeInTheDocument();
});

it('redirects to login when "Continue to Login" button is clicked', () => {
global.window = Object.create(window);
const mockHref = jest.fn();
Object.defineProperty(window, 'location', {
value: { href: '', assign: mockHref },
writable: true,
});

render(<Page />);
const loginButton = screen.getByRole('button', { name: /Continue to Login/i });
fireEvent.click(loginButton);

Object.defineProperty(window.location, 'href', {
set: mockHref,
});

});

it('redirects to home when "Return to Home" button is clicked', () => {
global.window = Object.create(window);
const mockHref = jest.fn();
Object.defineProperty(window, 'location', {
value: { href: '', assign: mockHref },
writable: true,
});

render(<Page />);
const homeButton = screen.getByRole('button', { name: /Return to Home/i });
fireEvent.click(homeButton);

expect(mockHref).toHaveBeenCalledWith('/');
});
});
15 changes: 2 additions & 13 deletions src/app/error/verify-email/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ export default function Page() {
<div className="min-h-[75vh] flex items-center justify-center p-4">
<div className="w-full max-w-md bg-white rounded-lg shadow-lg p-6">
<div className="text-center space-y-6">
{/* Icon */}
<div className="mx-auto w-16 h-16 rounded-full flex items-center justify-center bg-gray-100">
<Mail className="w-8 h-8" />
</div>

{/* Title and Description */}
<div className="space-y-2">
<h2 className="text-2xl font-bold tracking-tight">
Verify your email
Expand All @@ -22,8 +19,6 @@ export default function Page() {
continue.
</p>
</div>

{/* Steps */}
<div className="space-y-4 py-4">
{[
"Open your email inbox",
Expand All @@ -38,8 +33,6 @@ export default function Page() {
</div>
))}
</div>

{/* Divider */}
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-200"></div>
Expand All @@ -48,8 +41,6 @@ export default function Page() {
<span className="px-2 bg-white text-gray-500">Already verified?</span>
</div>
</div>

{/* Actions */}
<div className="space-y-3">
<button
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors flex items-center justify-center group"
Expand All @@ -63,13 +54,11 @@ export default function Page() {

<button
className="w-full border border-gray-300 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-50 transition-colors"
onClick={() => (window.location.href = "/")}
onClick={() => window.location.assign("/")}
>
Return to Home
</button>
</div>

{/* Help text */}
<p className="text-xs text-gray-500">
Didnt receive the email? Check your spam folder or try logging in
again to resend the verification email.
Expand All @@ -78,4 +67,4 @@ export default function Page() {
</div>
</div>
);
}
}
2 changes: 1 addition & 1 deletion src/app/verification/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const Verification = () => {
useEffect(() => {
setTimeout(() => {
router.push('/api/auth/login');
}, 60000);
}, 6000);
}, [router]);

return (
Expand Down
7 changes: 4 additions & 3 deletions src/components/MainCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import React from 'react';
interface MainCardProps {
children: React.ReactNode;
}

const MainCard: React.FC<MainCardProps> = ({ children }) => {
return (
<div className="w-full max-w-5xl mx-auto bg-white rounded-2xl shadow-lg border border-gray-200 grid grid-cols-1 md:grid-cols-2 overflow-hidden">
{children}
<div className="w-full h-full flex items-center justify-center">
<div className="w-full max-w-2xl mx-auto p-8">
{children}
</div>
</div>
);
};
Expand Down
51 changes: 51 additions & 0 deletions src/lib/schema/error.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { z } from "zod";
import { createError, createValidationError, Error } from './error';

describe('Error Schema', () => {
it('should validate a correct error object', () => {
const errorObject = {
message: "An error occurred",
errors: [
{ field: "username", message: "Username is required" },
{ field: "password", message: "Password is required" }
]
};

expect(() => Error.parse(errorObject)).not.toThrow();
});

it('should throw an error for an invalid error object', () => {
const invalidErrorObject = {
message: "",
errors: [
{ field: "username", message: "" }
]
};

expect(() => Error.parse(invalidErrorObject)).toThrow();
});
});

describe('createError', () => {
it('should create a valid error object', () => {
const errorObject = createError("An error occurred", [
{ message: "Username is required" },
{ message: "Password is required" }
]);

expect(() => Error.parse(errorObject)).not.toThrow();
});
});

describe('createValidationError', () => {
it('should create a valid validation error object', () => {
const validationError = new z.ZodError([
{ path: ["username"], message: "Username is required", code: "custom" },
{ path: ["password"], message: "Password is required", code: "custom" }
]);

const errorObject = createValidationError(validationError);

expect(() => Error.parse(errorObject)).not.toThrow();
});
});

0 comments on commit baf4a0c

Please sign in to comment.