diff --git a/apps/api/package.json b/apps/api/package.json index 1ce77b8..83fb984 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "predev": "npm run build --workspace @rentaldrivego/types", - "dev": "node ../../scripts/run-with-env-file.cjs ../../.env.local ../../node_modules/.bin/ts-node-dev --respawn --transpile-only src/index.ts", + "dev": "node ../../scripts/run-with-env-file.cjs ../../.env.local ../../node_modules/.bin/ts-node-dev --respawn --transpile-only --ignore-watch ../../packages/types/dist src/index.ts", "prebuild": "npm run build --workspace @rentaldrivego/types", "build": "tsc", "prestart": "npm run build --workspace @rentaldrivego/types", diff --git a/apps/dashboard/src/app/(public)/sign-in/page.tsx b/apps/dashboard/src/app/(public)/sign-in/page.tsx index 93b5d5d..55021f0 100644 --- a/apps/dashboard/src/app/(public)/sign-in/page.tsx +++ b/apps/dashboard/src/app/(public)/sign-in/page.tsx @@ -1,16 +1,6 @@ import { Suspense } from 'react' -import { headers } from 'next/headers' -import { redirect } from 'next/navigation' import SignInPageClient from '@/app/sign-in/[[...sign-in]]/SignInPageClient' -const MARKETPLACE_URL = process.env.NEXT_PUBLIC_MARKETPLACE_URL ?? 'http://localhost:3000' - -function isInternalHost(host: string | null): boolean { - if (!host) return false - const hostname = host.split(':')[0]?.toLowerCase() - return ['host.docker.internal', 'dashboard', 'admin', 'api', 'marketplace'].includes(hostname) -} - export default async function SignInPage({ searchParams, }: { @@ -19,35 +9,6 @@ export default async function SignInPage({ const params = await searchParams const embedded = params.embedded === '1' - if (!embedded) { - const requestHeaders = headers() - const host = requestHeaders.get('host') - const forwardedHost = requestHeaders.get('x-forwarded-host') - const proto = requestHeaders.get('x-forwarded-proto') ?? 'http' - const query = new URLSearchParams() - - for (const [key, value] of Object.entries(params)) { - if (value === undefined) continue - if (Array.isArray(value)) { - for (const item of value) query.append(key, item) - } else { - query.set(key, value) - } - } - - const marketplaceOrigin = new URL(MARKETPLACE_URL) - const publicHost = forwardedHost && !isInternalHost(forwardedHost) - ? forwardedHost - : host && !isInternalHost(host) - ? host - : null - - if (!publicHost || publicHost === marketplaceOrigin.host) { - const destination = `${MARKETPLACE_URL}/sign-in${query.size ? `?${query.toString()}` : ''}` - redirect(destination) - } - } - return ( diff --git a/apps/marketplace/src/app/(public)/sign-in/page.tsx b/apps/marketplace/src/app/(public)/sign-in/page.tsx deleted file mode 100644 index de8b0dc..0000000 --- a/apps/marketplace/src/app/(public)/sign-in/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import WorkspaceFrame from '@/components/WorkspaceFrame' - -export default function SignInPage() { - return -} diff --git a/apps/marketplace/src/app/layout.tsx b/apps/marketplace/src/app/layout.tsx index b0c611c..f7793be 100644 --- a/apps/marketplace/src/app/layout.tsx +++ b/apps/marketplace/src/app/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata } from 'next' +import { cookies } from 'next/headers' import MarketplaceShell from '@/components/MarketplaceShell' import { getMarketplaceLanguage } from '@/lib/i18n.server' import './globals.css' @@ -15,6 +16,12 @@ export const metadata: Metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { const language = getMarketplaceLanguage() + const cookieStore = cookies() + const rawTheme = + cookieStore.get('rentaldrivego-theme')?.value ?? + cookieStore.get('marketplace-theme')?.value + const theme = rawTheme === 'dark' ? 'dark' : 'light' + return ( @@ -27,7 +34,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) /> - {children} + {children} ) diff --git a/apps/marketplace/src/app/sign-in/page.tsx b/apps/marketplace/src/app/sign-in/page.tsx new file mode 100644 index 0000000..97de6f9 --- /dev/null +++ b/apps/marketplace/src/app/sign-in/page.tsx @@ -0,0 +1,39 @@ +import { headers } from 'next/headers' +import { redirect } from 'next/navigation' +import { resolveServerAppUrl } from '@/lib/appUrls' + +type SearchParams = Record + +function buildSearch(params: SearchParams): string { + const nextParams = new URLSearchParams() + + for (const [key, value] of Object.entries(params)) { + if (typeof value === 'string') { + nextParams.set(key, value) + continue + } + + if (Array.isArray(value)) { + for (const entry of value) nextParams.append(key, entry) + } + } + + const search = nextParams.toString() + return search ? `?${search}` : '' +} + +export default async function SignInPage({ + searchParams, +}: { + searchParams: SearchParams +}) { + const requestHeaders = headers() + const dashboardUrl = resolveServerAppUrl( + process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3000/dashboard', + requestHeaders.get('host'), + requestHeaders.get('x-forwarded-proto'), + ) + const params = searchParams + + redirect(`${dashboardUrl}/sign-in${buildSearch(params)}`) +} diff --git a/apps/marketplace/src/components/MarketplaceHeader.tsx b/apps/marketplace/src/components/MarketplaceHeader.tsx index f859473..c41875a 100644 --- a/apps/marketplace/src/components/MarketplaceHeader.tsx +++ b/apps/marketplace/src/components/MarketplaceHeader.tsx @@ -81,6 +81,11 @@ export default function MarketplaceHeader({ setThemeMenuOpen((open) => !open) } + const signInParams = new URLSearchParams() + signInParams.set('lang', currentLanguage.value) + signInParams.set('theme', theme) + const signInHref = `${dashboardUrl}/sign-in?${signInParams.toString()}` + return (
@@ -113,7 +118,7 @@ export default function MarketplaceHeader({ {dict.explore} {dict.signIn} diff --git a/apps/marketplace/src/components/MarketplaceShell.tsx b/apps/marketplace/src/components/MarketplaceShell.tsx index fb36f5b..bb0eac1 100644 --- a/apps/marketplace/src/components/MarketplaceShell.tsx +++ b/apps/marketplace/src/components/MarketplaceShell.tsx @@ -1,7 +1,7 @@ 'use client' import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react' -import { useRouter } from 'next/navigation' +import { usePathname, useRouter } from 'next/navigation' import { appPrivacyHref, footerPageHref } from '@/lib/footerContent' import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE, isMarketplaceLanguage, type MarketplaceLanguage } from '@/lib/i18n' import { SHARED_LANGUAGE_KEY, SHARED_THEME_KEY, readCurrentUserScopedPreference, readScopedPreference, writeScopedPreference } from '@/lib/preferences' @@ -157,14 +157,17 @@ export function useMarketplacePreferences() { export default function MarketplaceShell({ children, initialLanguage = 'en', + initialTheme = 'light', }: { children: React.ReactNode initialLanguage?: MarketplaceLanguage + initialTheme?: Theme }) { const router = useRouter() + const pathname = usePathname() const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1' const [language, setLanguageState] = useState(initialLanguage) - const [theme, setThemeState] = useState('light') + const [theme, setThemeState] = useState(initialTheme) const [hydrated, setHydrated] = useState(false) const previousLanguage = useRef(initialLanguage) const [companyName, setCompanyName] = useState(null) @@ -320,11 +323,11 @@ export default function MarketplaceShell({ try { sessionStorage.setItem('marketplace-language', language) } catch {} writeScopedPreference(SHARED_LANGUAGE_KEY, language, ['marketplace-language']) - if (previousLanguage.current !== language) { + if (previousLanguage.current !== language && pathname !== '/sign-in') { router.refresh() } previousLanguage.current = language - }, [hydrated, language, router]) + }, [hydrated, language, pathname, router]) useEffect(() => { document.documentElement.classList.toggle('dark', theme === 'dark') diff --git a/apps/marketplace/src/components/WorkspaceFrame.tsx b/apps/marketplace/src/components/WorkspaceFrame.tsx index b499198..d889736 100644 --- a/apps/marketplace/src/components/WorkspaceFrame.tsx +++ b/apps/marketplace/src/components/WorkspaceFrame.tsx @@ -94,33 +94,39 @@ export default function WorkspaceFrame({ target }: { target: FrameId }) { }, [target]) useEffect(() => { + if (target === 'sign-in' && src) return + const nextUrl = new URL(buildFrameUrl(config.appUrl, framePath)) nextUrl.searchParams.set('theme', theme) nextUrl.searchParams.set('lang', language) nextUrl.searchParams.set('embedded', '1') setSrc(nextUrl.toString()) - }, [config.appUrl, framePath, language, theme]) + }, [config.appUrl, framePath, language, src, target, theme]) + + const content = src ? ( +