fix frontend
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<Suspense fallback={null}>
|
||||
<SignInPageClient embedded={embedded} />
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import WorkspaceFrame from '@/components/WorkspaceFrame'
|
||||
|
||||
export default function SignInPage() {
|
||||
return <WorkspaceFrame target="sign-in" />
|
||||
}
|
||||
@@ -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 (
|
||||
<html lang={language} dir={language === 'ar' ? 'rtl' : 'ltr'} suppressHydrationWarning>
|
||||
<head>
|
||||
@@ -27,7 +34,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
/>
|
||||
</head>
|
||||
<body suppressHydrationWarning>
|
||||
<MarketplaceShell initialLanguage={language}>{children}</MarketplaceShell>
|
||||
<MarketplaceShell initialLanguage={language} initialTheme={theme}>{children}</MarketplaceShell>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { headers } from 'next/headers'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { resolveServerAppUrl } from '@/lib/appUrls'
|
||||
|
||||
type SearchParams = Record<string, string | string[] | undefined>
|
||||
|
||||
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)}`)
|
||||
}
|
||||
@@ -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 (
|
||||
<header className="sticky top-0 z-50 border-b border-stone-200/80 bg-white/78 backdrop-blur-xl shadow-[0_10px_30px_rgba(15,23,42,0.08)] transition-colors dark:border-blue-900 dark:bg-[#07101e]/76 dark:shadow-[0_10px_30px_rgba(0,0,0,0.32)]">
|
||||
<div className="shell flex min-h-14 flex-col gap-1.5 py-1.5 lg:flex-row lg:items-center lg:justify-between lg:gap-3 lg:py-2">
|
||||
@@ -113,7 +118,7 @@ export default function MarketplaceHeader({
|
||||
{dict.explore}
|
||||
</Link>
|
||||
<Link
|
||||
href="/sign-in"
|
||||
href={signInHref}
|
||||
className="rounded-full px-2.5 py-1 text-[12px] font-medium text-stone-600 transition hover:bg-stone-100 hover:text-stone-900 dark:text-stone-300 dark:hover:bg-[#162038] dark:hover:text-stone-100 sm:px-4 sm:py-2 sm:text-sm"
|
||||
>
|
||||
{dict.signIn}
|
||||
|
||||
@@ -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<MarketplaceLanguage>(initialLanguage)
|
||||
const [theme, setThemeState] = useState<Theme>('light')
|
||||
const [theme, setThemeState] = useState<Theme>(initialTheme)
|
||||
const [hydrated, setHydrated] = useState(false)
|
||||
const previousLanguage = useRef<MarketplaceLanguage>(initialLanguage)
|
||||
const [companyName, setCompanyName] = useState<string | null>(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')
|
||||
|
||||
@@ -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 ? (
|
||||
<iframe
|
||||
title={config.label}
|
||||
src={src}
|
||||
className="block w-full border-0 bg-white dark:bg-[#07101e]"
|
||||
style={{ height: FRAME_HEIGHT }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="flex items-center justify-center text-sm text-stone-500 dark:text-stone-300"
|
||||
style={{ height: FRAME_HEIGHT }}
|
||||
>
|
||||
{loadingLabel} {config.label}…
|
||||
</div>
|
||||
)
|
||||
|
||||
if (target === 'sign-in') {
|
||||
return <main className="min-h-[calc(100vh-56px)]">{content}</main>
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="site-page">
|
||||
<div className="site-section py-6 sm:py-8 lg:py-10">
|
||||
<section className="site-panel overflow-hidden p-0">
|
||||
{src ? (
|
||||
<iframe
|
||||
title={config.label}
|
||||
src={src}
|
||||
className="w-full border-0 bg-white dark:bg-[#07101e]"
|
||||
style={{ height: FRAME_HEIGHT }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="flex items-center justify-center text-sm text-stone-500 dark:text-stone-300"
|
||||
style={{ height: FRAME_HEIGHT }}
|
||||
>
|
||||
{loadingLabel} {config.label}…
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
<section className="site-panel overflow-hidden p-0">{content}</section>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
|
||||
+10
-4
@@ -92,7 +92,7 @@ services:
|
||||
- .env.docker.dev
|
||||
environment:
|
||||
FILE_STORAGE_ROOT: /var/lib/rentaldrivego/storage
|
||||
command: ["sh", "-c", "npm run build --workspace @rentaldrivego/types && cd /app/apps/api && exec /app/node_modules/.bin/ts-node-dev --respawn --transpile-only src/index.ts"]
|
||||
command: ["sh", "-c", "npm run build --workspace @rentaldrivego/types && cd /app/apps/api && exec /app/node_modules/.bin/ts-node-dev --respawn --transpile-only --ignore-watch ../../packages/types/dist src/index.ts"]
|
||||
ports:
|
||||
- "4000:4000"
|
||||
volumes:
|
||||
@@ -114,13 +114,14 @@ services:
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
"npm run build --workspace @rentaldrivego/types && cd /app/apps/marketplace && rm -rf .next && exec env PATH=/app/docker/scripts:$PATH /app/node_modules/.bin/next dev -H 0.0.0.0 -p 3000",
|
||||
"npm run build --workspace @rentaldrivego/types && cd /app/apps/marketplace && mkdir -p .next && find .next -mindepth 1 -exec rm -rf {} + && exec env PATH=/app/docker/scripts:$PATH /app/node_modules/.bin/next dev -H 0.0.0.0 -p 3000",
|
||||
]
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- .:/app
|
||||
- app_node_modules:/app/node_modules
|
||||
- marketplace_next:/app/apps/marketplace/.next
|
||||
restart: unless-stopped
|
||||
profiles: ["marketplace", "full"]
|
||||
|
||||
@@ -136,13 +137,14 @@ services:
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
"npm run build --workspace @rentaldrivego/types && cd /app/apps/dashboard && rm -rf .next && exec env PATH=/app/docker/scripts:$PATH /app/node_modules/.bin/next dev -H 0.0.0.0 -p 3001",
|
||||
"npm run build --workspace @rentaldrivego/types && cd /app/apps/dashboard && mkdir -p .next && find .next -mindepth 1 -exec rm -rf {} + && exec env PATH=/app/docker/scripts:$PATH /app/node_modules/.bin/next dev -H 0.0.0.0 -p 3001",
|
||||
]
|
||||
ports:
|
||||
- "3001:3001"
|
||||
volumes:
|
||||
- .:/app
|
||||
- app_node_modules:/app/node_modules
|
||||
- dashboard_next:/app/apps/dashboard/.next
|
||||
restart: unless-stopped
|
||||
profiles: ["dashboard", "full"]
|
||||
|
||||
@@ -158,13 +160,14 @@ services:
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
"npm run build --workspace @rentaldrivego/types && cd /app/apps/admin && rm -rf .next && exec env PATH=/app/docker/scripts:$PATH /app/node_modules/.bin/next dev -H 0.0.0.0 -p 3002",
|
||||
"npm run build --workspace @rentaldrivego/types && cd /app/apps/admin && mkdir -p .next && find .next -mindepth 1 -exec rm -rf {} + && exec env PATH=/app/docker/scripts:$PATH /app/node_modules/.bin/next dev -H 0.0.0.0 -p 3002",
|
||||
]
|
||||
ports:
|
||||
- "3002:3002"
|
||||
volumes:
|
||||
- .:/app
|
||||
- app_node_modules:/app/node_modules
|
||||
- admin_next:/app/apps/admin/.next
|
||||
restart: unless-stopped
|
||||
profiles: ["admin", "full"]
|
||||
|
||||
@@ -176,3 +179,6 @@ volumes:
|
||||
app_node_modules:
|
||||
postgres_bootstrap_state:
|
||||
api_uploads_dev:
|
||||
marketplace_next:
|
||||
dashboard_next:
|
||||
admin_next:
|
||||
|
||||
Reference in New Issue
Block a user