diff --git a/src/apps/gigs/README.md b/src/apps/gigs/README.md
index 9d40a7d0d..979a00c47 100644
--- a/src/apps/gigs/README.md
+++ b/src/apps/gigs/README.md
@@ -6,6 +6,8 @@ This sub-app ports the public community-app Gig Work flow into platform-ui:
featured-first created/updated ordering, hotlist and ten jobs per result page.
- `/gigs/:slug`: compensation, location, duration, weekly hours, timezone,
required skills, description, eligibility notes and application handoff.
+ Profile, forum and opportunity advice links open in new tabs, preserving the
+ gig details page. Gig navigation and email links keep their existing behavior.
- `/gigs/:slug/apply`: sign-in with the full return URL, candidate prefill,
resume upload, skill autocomplete/custom skills, weekly pay expectation,
referral source, availability confirmations, policy dialogs and application
@@ -60,12 +62,15 @@ yarn build
yarn test:no-watch --runInBand --watch=false --runTestsByPath \
src/apps/gigs/src/gigs.utils.spec.ts \
src/apps/gigs/src/gigs.service.spec.ts \
+ src/apps/gigs/src/pages/GigDetailsPage.spec.tsx \
src/apps/gigs/src/components/GigApplicationForm.spec.tsx
```
The tests cover discovery rules, salary fallbacks, required fields, consent and
availability, upload limits, legacy payload mapping, HTTP-200 error envelopes,
expired authentication, prefill, submission retry and already-placed candidates.
+Gig detail link tests cover new-tab advice links for signed-in and anonymous
+visitors, plus existing gig navigation and email destinations.
Also verify the listing, detail and anonymous apply route against real Recruit
reads in a browser at desktop and mobile widths. Authenticated submission tests
use mocks so verification does not create real candidates or send recruiter
diff --git a/src/apps/gigs/src/pages/GigDetailsPage.spec.tsx b/src/apps/gigs/src/pages/GigDetailsPage.spec.tsx
new file mode 100644
index 000000000..52fe29d2a
--- /dev/null
+++ b/src/apps/gigs/src/pages/GigDetailsPage.spec.tsx
@@ -0,0 +1,114 @@
+/* eslint-disable import/no-extraneous-dependencies */
+import { MemoryRouter, Route, Routes } from 'react-router-dom'
+import useSWR from 'swr'
+
+import { render, screen } from '@testing-library/react'
+import { profileContext, ProfileContextData, UserProfile } from '~/libs/core'
+import '@testing-library/jest-dom'
+
+import { Gig } from '../models'
+
+import GigDetailsPage from './GigDetailsPage'
+
+jest.mock('swr')
+jest.mock('~/config', () => ({
+ EnvironmentConfig: {
+ TC_DOMAIN: 'topcoder-dev.com',
+ URLS: { ACCOUNT_SETTINGS: 'https://accounts.topcoder-dev.com/settings' },
+ USER_PROFILE_URL: 'https://profiles.topcoder-dev.com',
+ },
+}), { virtual: true })
+jest.mock('~/libs/core', () => {
+ const ReactModule: typeof import('react') = jest.requireActual('react')
+ return { profileContext: ReactModule.createContext({}) }
+}, { virtual: true })
+jest.mock('~/libs/ui', () => ({
+ PageTitle: (): JSX.Element => <>>,
+}), { virtual: true })
+jest.mock('../gigs.service', () => ({
+ ...jest.requireActual('../gigs.service'),
+ getGig: jest.fn(),
+}))
+jest.mock('../components/GigShared', () => ({
+ GigContent: (props: { text: string }): JSX.Element =>
{props.text}
,
+ GigFacts: (): JSX.Element => <>>,
+ GigState: (props: { title: string }): JSX.Element => {props.title}
,
+}))
+
+const job: Gig = {
+ enable_job_application_form: 1,
+ job_description_text: 'Test job description',
+ job_status: { id: 1 },
+ name: 'Test gig',
+ slug: 'test-gig',
+}
+
+/**
+ * Renders the gig details route with an optional member handle for navigation regression tests.
+ *
+ * @param handle Signed-in member handle; omitted to render the anonymous profile destination.
+ * @returns Nothing; assertions query the rendered document.
+ * @throws Propagates rendering errors so the test fails.
+ */
+function renderDetails(handle?: string): void {
+ render(
+
+
+
+ } />
+
+
+ ,
+ )
+}
+
+describe('Gig details links', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ jest.mocked(useSWR)
+ .mockReturnValue({ data: job, error: undefined, isValidating: false, mutate: jest.fn() })
+ })
+
+ it.each([
+ ['qa member', 'https://profiles.topcoder-dev.com/qa%20member'],
+ [undefined, 'https://accounts.topcoder-dev.com/settings'],
+ ])('opens advice links in new tabs for member handle %s', (handle, profileUrl) => {
+ renderDetails(handle)
+
+ const links = [
+ ['Update your profile', profileUrl],
+ ['Visit the Gig Work forum', 'https://vanilla.topcoder-dev.com/categories/gig-work-discusssions'],
+ ['Browse opportunities', '/opportunities'],
+ ]
+ links.forEach(([name, href]) => {
+ const link = screen.getByRole('link', { name })
+ expect(link)
+ .toHaveAttribute('href', href)
+ expect(link)
+ .toHaveAttribute('target', '_blank')
+ expect(link)
+ .toHaveAttribute('rel', 'noopener noreferrer')
+ })
+ })
+
+ it('preserves gig navigation and email destinations', () => {
+ renderDetails()
+
+ const links = [
+ ['← All gigs', '/gigs'],
+ ['Apply to this job', '/gigs/test-gig/apply'],
+ ['View other gigs', '/gigs'],
+ ['talent.taas@wipro.com', 'mailto:talent.taas@wipro.com'],
+ ['Contact the Gig Work team', 'mailto:talent.taas@wipro.com'],
+ ]
+ links.forEach(([name, href]) => {
+ const link = screen.getByRole('link', { name })
+ expect(link)
+ .toHaveAttribute('href', href)
+ expect(link)
+ .not.toHaveAttribute('target')
+ })
+ })
+})
diff --git a/src/apps/gigs/src/pages/GigDetailsPage.tsx b/src/apps/gigs/src/pages/GigDetailsPage.tsx
index 56ee5e74e..7eac867f0 100644
--- a/src/apps/gigs/src/pages/GigDetailsPage.tsx
+++ b/src/apps/gigs/src/pages/GigDetailsPage.tsx
@@ -12,7 +12,10 @@ import { GigContent, GigFacts, GigState } from '../components/GigShared'
import { getGig, RecruitError } from '../gigs.service'
import { gigSkills, GIGS_PATH, isOpenGig } from '../gigs.utils'
-/** Shows job facts, description, eligibility notes and the application handoff, including closed/error states. */
+/**
+ * Shows job facts, description, eligibility notes and the application handoff, including closed/error states.
+ * Opens profile, forum and opportunity advice links in new tabs so members can keep the gig details open.
+ */
const GigDetailsPage: FC = () => {
const { slug = '' }: { slug?: string } = useParams<{ slug: string }>()
const { profile }: ProfileContextData = useContext(profileContext)
@@ -105,6 +108,8 @@ const GigDetailsPage: FC = () => {
)}`
: EnvironmentConfig.URLS.ACCOUNT_SETTINGS
}
+ target='_blank'
+ rel='noopener noreferrer'
>
Update your profile
@@ -115,6 +120,8 @@ const GigDetailsPage: FC = () => {
Visit the Gig Work forum
@@ -124,7 +131,9 @@ const GigDetailsPage: FC = () => {
Participate in Topcoder competitions to demonstrate what you can do.
- Browse opportunities
+
+ Browse opportunities
+