generated from Real-Dev-Squad/website-template
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add page for redirect to original url (#40)
* feat: add functionality for redirect ot original url * refactor: redirect page * refactor: redirect page and add visiticon for shorten url * fix: typo in redirect page * test: add test for changes * refactor and fix test cases * refactor : change anchor tag to Link tag * test: fix failing test * Update index.tsx
- Loading branch information
1 parent
1be31df
commit 9d33245
Showing
12 changed files
with
317 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; | ||
|
||
import Redirect from '../../src/pages/[redirect]/index'; | ||
import { useRouter } from 'next/router'; | ||
|
||
jest.mock('next/router', () => ({ | ||
useRouter: jest.fn(), | ||
})); | ||
|
||
describe('Redirect Component', () => { | ||
const mockRouterPush = jest.fn(); | ||
const mockRouterReplace = jest.fn(); | ||
const mockRouter = { | ||
push: mockRouterPush, | ||
replace: mockRouterReplace, | ||
query: { redirect: 'ffdsfds' }, | ||
}; | ||
|
||
beforeEach(() => { | ||
(useRouter as jest.Mock).mockReturnValue(mockRouter); | ||
}); | ||
|
||
test('redirects to original URL on Go button click', async () => { | ||
render(<Redirect />); | ||
const goButton = screen.getByText('Go'); | ||
await act(async () => { | ||
fireEvent.click(goButton); | ||
}); | ||
expect(mockRouterPush).toHaveBeenCalled(); | ||
}); | ||
|
||
test('show tooltip on Go button click', async () => { | ||
render(<Redirect />); | ||
const goButton = screen.getByText('Go'); | ||
await act(async () => { | ||
fireEvent.click(goButton); | ||
}); | ||
expect(mockRouterPush).toHaveBeenCalled(); | ||
const tooltip = screen.getByText('The skip feature is exclusively available to Premium users.'); | ||
expect(tooltip).toBeInTheDocument(); | ||
}); | ||
|
||
test('redirects when timer reaches zero', async () => { | ||
jest.useFakeTimers(); | ||
render(<Redirect />); | ||
act(() => jest.advanceTimersByTime(5000)); | ||
waitFor(() => expect(mockRouterPush).toHaveBeenCalled()); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import fetchMock from 'jest-fetch-mock'; | ||
import fetchOriginalUrl from '../../src/utils/fetchOriginalUrl'; | ||
import { act } from 'react-dom/test-utils'; | ||
|
||
fetchMock.enableMocks(); | ||
|
||
describe('fetchOriginalUrl', () => { | ||
const shortUrlCode = '442d39ac'; | ||
const originalUrl = 'https://github.com/Real-Dev-Squad/tiny-site-frontend/pull/40'; | ||
|
||
it('fetches and displays the original URL if the response is successful', async () => { | ||
const responseData = { url: { OriginalUrl: originalUrl } }; | ||
|
||
fetchMock.mockResponse(JSON.stringify(responseData), { status: 200 }); | ||
|
||
await act(async () => { | ||
const result = await fetchOriginalUrl(shortUrlCode); | ||
expect(result).toEqual(originalUrl); | ||
}); | ||
}); | ||
|
||
it('returns null if the response is not successful', async () => { | ||
fetchMock.mockResponse('', { status: 404 }); | ||
|
||
await act(async () => { | ||
const result = await fetchOriginalUrl(shortUrlCode); | ||
expect(result).toBeNull(); | ||
}); | ||
}); | ||
|
||
it('handles errors gracefully', async () => { | ||
fetchMock.mockReject(new Error('Network error')); | ||
|
||
await act(async () => { | ||
const result = await fetchOriginalUrl(shortUrlCode); | ||
expect(result).toBeNull(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
const RedirectIcon = () => ( | ||
<svg xmlns="http://www.w3.org/2000/svg" height="5em" viewBox="0 0 512 512" fill="#ffff"> | ||
<path d="M227.7 11.7c15.6-15.6 40.9-15.6 56.6 0l216 216c15.6 15.6 15.6 40.9 0 56.6l-216 216c-15.6 15.6-40.9 15.6-56.6 0l-216-216c-15.6-15.6-15.6-40.9 0-56.6l216-216zm87.6 137c-4.6-4.6-11.5-5.9-17.4-3.5s-9.9 8.3-9.9 14.8v56H224c-35.3 0-64 28.7-64 64v48c0 13.3 10.7 24 24 24s24-10.7 24-24V280c0-8.8 7.2-16 16-16h64v56c0 6.5 3.9 12.3 9.9 14.8s12.9 1.1 17.4-3.5l80-80c6.2-6.2 6.2-16.4 0-22.6l-80-80z" /> | ||
</svg> | ||
); | ||
|
||
export default RedirectIcon; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
const ShareIcon = () => ( | ||
<svg | ||
version="1.0" | ||
xmlns="http://www.w3.org/2000/svg" | ||
width="2em" | ||
height="2em" | ||
viewBox="0 0 513.000000 522.000000" | ||
fill="#ffff" | ||
> | ||
<title>Visit</title> | ||
<g transform="translate(0.000000,522.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"> | ||
<path | ||
d="M2710 4616 l0 -606 -22 -4 c-13 -2 -77 -9 -143 -16 -501 -49 -995 | ||
-249 -1430 -579 -142 -107 -421 -390 -531 -536 -348 -465 -535 -968 -574 | ||
-1544 -7 -90 -10 -382 -8 -691 l3 -534 184 424 c201 466 247 561 356 725 358 | ||
542 894 941 1530 1138 160 50 400 95 582 110 l53 4 2 -605 3 -604 365 298 | ||
c201 165 740 605 1198 978 458 374 832 683 832 687 0 3 -57 53 -127 110 -345 | ||
280 -2184 1777 -2225 1812 l-48 39 0 -606z m1124 -696 c443 -360 805 -657 805 | ||
-660 0 -3 -365 -303 -812 -667 l-812 -663 -3 440 -2 440 -138 0 c-848 -1 | ||
-1640 -340 -2232 -956 -98 -102 -240 -274 -302 -364 l-17 -25 6 40 c25 189 96 | ||
446 179 642 294 702 918 1255 1649 1462 238 67 391 90 663 98 l192 6 0 434 c0 | ||
271 4 433 10 431 5 -1 371 -298 814 -658z" | ||
/> | ||
</g> | ||
</svg> | ||
); | ||
|
||
export default ShareIcon; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
export const TINY_API_URL = 'https://staging-tinysite-api.realdevsquad.com/v1'; | ||
export const TINY_API_GOOGLE_LOGIN = `${TINY_API_URL}/auth/google/login`; | ||
export const TINY_API_LOGOUT = `${TINY_API_URL}/auth/logout`; | ||
export const BASE_SHORT_URL = 'https://staging-tinysite.realdevsquad.com'; | ||
export const TINY_SITE = 'https://staging-tinysite.realdevsquad.com'; | ||
export const TINY_API_URL_DETAIL = `${TINY_API_URL}/urls`; | ||
export const TINY_API_REDIRECT = `${TINY_API_URL}/tinyurl`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { useEffect, useState } from 'react'; | ||
|
||
import Head from 'next/head'; | ||
import Link from 'next/link'; | ||
import RedirectIcon from '../../../public/assets/icons/redirect'; | ||
import { TINY_SITE } from '@/constants/url'; | ||
import fetchOriginalUrl from '@/utils/fetchOriginalUrl'; | ||
import { useRouter } from 'next/router'; | ||
|
||
interface LoaderTimerProps { | ||
timer: number; | ||
goButtonClickHandler: () => void; | ||
} | ||
|
||
const LoaderTimer = ({ timer, goButtonClickHandler }: LoaderTimerProps) => { | ||
if (timer < 1) { | ||
return ( | ||
<div className="mt-4 flex flex-col items-center space-y-2"> | ||
<RedirectIcon /> | ||
<p className="text-1xl">Redirecting...</p> | ||
</div> | ||
); | ||
} else { | ||
return ( | ||
<> | ||
<div className="loader border-t-4 rounded-full border-gray-500 animate-spin aspect-square w-20 flex justify-center items-center text-yellow-700 text-4xl font-bold mt-4"> | ||
{timer} | ||
</div> | ||
<button onClick={goButtonClickHandler} className="mt-4 p-2 bg-blue-500 text-white rounded-md"> | ||
Go | ||
</button> | ||
</> | ||
); | ||
} | ||
}; | ||
|
||
const RedirectFooter = () => ( | ||
<div className="absolute bottom-0 right-0 p-2 text-gray-500 w-screen flex justify-center items-center"> | ||
<Link | ||
className="text-sm text-gray-400 font-bold cursor-pointer hover:underline" | ||
href={TINY_SITE} | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
By <span className="font-bold">Real Dev Squad</span> | ||
</Link> | ||
</div> | ||
); | ||
|
||
const Redirect = () => { | ||
const router = useRouter(); | ||
const { redirect: shortUrlCode } = router.query as { redirect: string }; | ||
const [originalUrl, setOriginalUrl] = useState(''); | ||
const [timer, setTimer] = useState(5); | ||
const [isPremiumUser] = useState(false); | ||
const [showTooltip, setShowTooltip] = useState(false); | ||
|
||
useEffect(() => { | ||
if (shortUrlCode) { | ||
const fetchOriginalUrlAsync = async () => { | ||
try { | ||
const url = await fetchOriginalUrl(shortUrlCode); | ||
if (url) { | ||
setOriginalUrl(url); | ||
} else { | ||
router.push('/'); | ||
} | ||
} catch (error) { | ||
console.error('Error fetching original URL:', error); | ||
} | ||
}; | ||
fetchOriginalUrlAsync(); | ||
} | ||
|
||
if (timer > 0) { | ||
const countdown = setTimeout(() => setTimer(timer - 1), 1000); | ||
return () => clearTimeout(countdown); | ||
} else if (timer === 0) { | ||
router.push(originalUrl); | ||
} | ||
}, [shortUrlCode, timer, originalUrl]); | ||
|
||
const handleGoButtonClick = () => { | ||
if (isPremiumUser) { | ||
router.push(originalUrl); | ||
} else { | ||
setShowTooltip(true); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
<Head> | ||
<title>Redirecting...</title> | ||
<meta name="robots" content="noindex" /> | ||
</Head> | ||
<div className="w-screen min-h-screen flex flex-col items-center justify-center bg-gray-900 text-white p-4"> | ||
<p className="text-lg">You are being redirected to:</p> | ||
<p className="text-blue-500 text-xl font-bold w-1/2 text-center truncate xl:w-1/2">{originalUrl}</p> | ||
<LoaderTimer timer={timer} goButtonClickHandler={handleGoButtonClick} /> | ||
|
||
{showTooltip && !isPremiumUser && ( | ||
<div className="mt-2 p-2 bg-yellow-100 text-yellow-800 rounded-md"> | ||
The skip feature is exclusively available to Premium users. | ||
</div> | ||
)} | ||
<RedirectFooter /> | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default Redirect; |
Oops, something went wrong.