website refactor
This commit is contained in:
@@ -17,12 +17,12 @@ export const viewport: Viewport = {
|
|||||||
maximumScale: 1,
|
maximumScale: 1,
|
||||||
userScalable: false,
|
userScalable: false,
|
||||||
viewportFit: 'cover',
|
viewportFit: 'cover',
|
||||||
|
themeColor: '#0a0a0a',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'GridPilot - SimRacing Platform',
|
title: 'GridPilot - SimRacing Platform',
|
||||||
description: 'The dedicated home for serious sim racing leagues. Automatic results, standings, team racing, and professional race control.',
|
description: 'The dedicated home for serious sim racing leagues. Automatic results, standings, team racing, and professional race control.',
|
||||||
themeColor: '#0a0a0a',
|
|
||||||
appleWebApp: {
|
appleWebApp: {
|
||||||
capable: true,
|
capable: true,
|
||||||
statusBarStyle: 'black-translucent',
|
statusBarStyle: 'black-translucent',
|
||||||
|
|||||||
@@ -607,7 +607,7 @@ export function CreateLeagueWizard({ stepName, onStepChange }: CreateLeagueWizar
|
|||||||
Create a new league
|
Create a new league
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text size="sm" color="text-gray-500" block>
|
<Text size="sm" color="text-gray-500" block>
|
||||||
We'll also set up your first season in {steps.length} easy steps.
|
We'll also set up your first season in {steps.length} easy steps.
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="xs" color="text-gray-500" block mt={1}>
|
<Text size="xs" color="text-gray-500" block mt={1}>
|
||||||
A league is your long-term brand. Each season is a block of races you can run again and again.
|
A league is your long-term brand. Each season is a block of races you can run again and again.
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||||
import { HomeTemplate, type HomeViewData } from '@/templates/HomeTemplate';
|
import { HomeTemplate, type HomeViewData } from '@/templates/HomeTemplate';
|
||||||
import { PageDataFetcher } from '@/lib/page/PageDataFetcher';
|
import { PageDataFetcher } from '@/lib/page/PageDataFetcher';
|
||||||
import { HomeService } from '@/lib/services/home/HomeService'; // @server-safe
|
import { HomePageQuery } from '@/lib/page-queries/HomePageQuery';
|
||||||
import { notFound, redirect } from 'next/navigation';
|
import { notFound, redirect } from 'next/navigation';
|
||||||
import { routes } from '@/lib/routing/RouteConfig';
|
import { routes } from '@/lib/routing/RouteConfig';
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const homeService = new HomeService();
|
if (await HomePageQuery.shouldRedirectToDashboard()) {
|
||||||
|
|
||||||
if (await homeService.shouldRedirectToDashboard()) {
|
|
||||||
redirect(routes.protected.dashboard);
|
redirect(routes.protected.dashboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await PageDataFetcher.fetchManual(async () => {
|
const data = await PageDataFetcher.fetchManual(async () => {
|
||||||
const result = await homeService.getHomeData();
|
const result = await HomePageQuery.execute();
|
||||||
return result.isOk() ? result.unwrap() : null;
|
return result.isOk() ? result.unwrap() : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { StatefulPageWrapper } from '@/components/shared/state/StatefulPageWrapper';
|
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||||
import { RaceResultsPageQuery } from '@/lib/page-queries/races/RaceResultsPageQuery';
|
import { RaceResultsPageQuery } from '@/lib/page-queries/races/RaceResultsPageQuery';
|
||||||
import RaceResultsPageClient from './RaceResultsPageClient';
|
import RaceResultsPageClient from './RaceResultsPageClient';
|
||||||
|
|
||||||
@@ -25,13 +25,12 @@ export default async function RaceResultsPage({ params }: RaceResultsPageProps)
|
|||||||
if (error === 'notFound') {
|
if (error === 'notFound') {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
// For other errors, let StatefulPageWrapper handle it
|
// For other errors, let PageWrapper handle it
|
||||||
return (
|
return (
|
||||||
<StatefulPageWrapper
|
<PageWrapper
|
||||||
data={undefined}
|
data={undefined}
|
||||||
Template={RaceResultsPageClient as any}
|
Template={RaceResultsPageClient as any}
|
||||||
error={new Error('Failed to load race results')}
|
error={new Error('Failed to load race results')}
|
||||||
retry={() => Promise.resolve()}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -39,10 +38,9 @@ export default async function RaceResultsPage({ params }: RaceResultsPageProps)
|
|||||||
const viewData = result.unwrap();
|
const viewData = result.unwrap();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatefulPageWrapper
|
<PageWrapper
|
||||||
data={viewData}
|
data={viewData}
|
||||||
Template={RaceResultsPageClient}
|
Template={RaceResultsPageClient}
|
||||||
retry={() => Promise.resolve()}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface BaseApiClientOptions {
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
retry?: boolean;
|
retry?: boolean;
|
||||||
retryConfig?: typeof DEFAULT_RETRY_CONFIG;
|
retryConfig?: typeof DEFAULT_RETRY_CONFIG;
|
||||||
|
allowUnauthenticated?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BaseApiClient {
|
export class BaseApiClient {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class DriversApiClient extends BaseApiClient {
|
|||||||
|
|
||||||
/** Get current driver (based on session) */
|
/** Get current driver (based on session) */
|
||||||
getCurrent(): Promise<GetDriverOutputDTO | null> {
|
getCurrent(): Promise<GetDriverOutputDTO | null> {
|
||||||
return this.get<GetDriverOutputDTO | null>('/drivers/current');
|
return this.get<GetDriverOutputDTO | null>('/drivers/current', { allowUnauthenticated: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get driver registration status for a specific race */
|
/** Get driver registration status for a specific race */
|
||||||
|
|||||||
24
apps/website/lib/builders/view-data/HomeViewDataBuilder.ts
Normal file
24
apps/website/lib/builders/view-data/HomeViewDataBuilder.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { HomeViewData } from '@/templates/HomeTemplate';
|
||||||
|
import type { HomeDataDTO } from '@/lib/types/dtos/HomeDataDTO';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HomeViewDataBuilder
|
||||||
|
*
|
||||||
|
* Transforms HomeDataDTO to HomeViewData.
|
||||||
|
*/
|
||||||
|
export class HomeViewDataBuilder {
|
||||||
|
/**
|
||||||
|
* Build HomeViewData from HomeDataDTO
|
||||||
|
*
|
||||||
|
* @param apiDto - The API DTO
|
||||||
|
* @returns HomeViewData
|
||||||
|
*/
|
||||||
|
static build(apiDto: HomeDataDTO): HomeViewData {
|
||||||
|
return {
|
||||||
|
isAlpha: apiDto.isAlpha,
|
||||||
|
upcomingRaces: apiDto.upcomingRaces,
|
||||||
|
topLeagues: apiDto.topLeagues,
|
||||||
|
teams: apiDto.teams,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,8 +30,10 @@ export function getWebsiteApiBaseUrl(): string {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDocker = process.env.DOCKER === 'true';
|
||||||
|
|
||||||
const fallback =
|
const fallback =
|
||||||
process.env.NODE_ENV === 'development'
|
process.env.NODE_ENV === 'development' && !isDocker
|
||||||
? 'http://localhost:3001'
|
? 'http://localhost:3001'
|
||||||
: 'http://api:3000';
|
: 'http://api:3000';
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
* Client: Reads from session context or provides mock implementation
|
* Client: Reads from session context or provides mock implementation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { getWebsiteApiBaseUrl } from '../config/apiBaseUrl';
|
||||||
|
|
||||||
// Server-side implementation
|
// Server-side implementation
|
||||||
export class FeatureFlagService {
|
export class FeatureFlagService {
|
||||||
private flags: Set<string>;
|
private flags: Set<string>;
|
||||||
@@ -41,11 +43,10 @@ export class FeatureFlagService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method to create service by fetching from API
|
* Factory method to create service by fetching from API
|
||||||
* Fetches from ${NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'}/features
|
|
||||||
* On error, returns empty flags (secure by default)
|
* On error, returns empty flags (secure by default)
|
||||||
*/
|
*/
|
||||||
static async fromAPI(): Promise<FeatureFlagService> {
|
static async fromAPI(): Promise<FeatureFlagService> {
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
|
const baseUrl = getWebsiteApiBaseUrl();
|
||||||
const url = `${baseUrl}/features`;
|
const url = `${baseUrl}/features`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
50
apps/website/lib/page-queries/HomePageQuery.ts
Normal file
50
apps/website/lib/page-queries/HomePageQuery.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Result } from '@/lib/contracts/Result';
|
||||||
|
import { HomeService } from '@/lib/services/home/HomeService';
|
||||||
|
import type { HomeViewData } from '@/templates/HomeTemplate';
|
||||||
|
import type { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||||
|
import { HomeViewDataBuilder } from '@/lib/builders/view-data/HomeViewDataBuilder';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HomePageQuery
|
||||||
|
*
|
||||||
|
* Server-side data fetcher for the home page.
|
||||||
|
* Returns Result<HomeViewData, string>
|
||||||
|
*/
|
||||||
|
export class HomePageQuery implements PageQuery<HomeViewData, void, string> {
|
||||||
|
/**
|
||||||
|
* Execute the home page query
|
||||||
|
*
|
||||||
|
* @returns Result with HomeViewData or error
|
||||||
|
*/
|
||||||
|
async execute(): Promise<Result<HomeViewData, string>> {
|
||||||
|
try {
|
||||||
|
const service = new HomeService();
|
||||||
|
const result = await service.getHomeData();
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
return Result.err('Error');
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewData = HomeViewDataBuilder.build(result.unwrap());
|
||||||
|
return Result.ok(viewData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('HomePageQuery failed:', error);
|
||||||
|
return Result.err('Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static execute for convenience
|
||||||
|
*/
|
||||||
|
static async execute(): Promise<Result<HomeViewData, string>> {
|
||||||
|
return new HomePageQuery().execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user should be redirected to dashboard
|
||||||
|
*/
|
||||||
|
static async shouldRedirectToDashboard(): Promise<boolean> {
|
||||||
|
const service = new HomeService();
|
||||||
|
return service.shouldRedirectToDashboard();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,10 @@ export interface ParsedWizardParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SearchParamParser {
|
export class SearchParamParser {
|
||||||
private static getParam(params: URLSearchParams | Record<string, string | string[] | undefined>, key: string): string | null {
|
private static getParam(params: URLSearchParams | Record<string, string | string[] | undefined> | undefined | null, key: string): string | null {
|
||||||
|
if (!params) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (params instanceof URLSearchParams) {
|
if (params instanceof URLSearchParams) {
|
||||||
return params.get(key);
|
return params.get(key);
|
||||||
}
|
}
|
||||||
@@ -54,7 +57,7 @@ export class SearchParamParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse auth parameters
|
// Parse auth parameters
|
||||||
static parseAuth(params: URLSearchParams | Record<string, string | string[] | undefined>): Result<ParsedAuthParams, string> {
|
static parseAuth(params: URLSearchParams | Record<string, string | string[] | undefined> | undefined | null): Result<ParsedAuthParams, string> {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
const returnTo = this.getParam(params, 'returnTo');
|
const returnTo = this.getParam(params, 'returnTo');
|
||||||
@@ -95,7 +98,7 @@ export class SearchParamParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse sponsor parameters
|
// Parse sponsor parameters
|
||||||
static parseSponsor(params: URLSearchParams | Record<string, string | string[] | undefined>): Result<ParsedSponsorParams, string> {
|
static parseSponsor(params: URLSearchParams | Record<string, string | string[] | undefined> | undefined | null): Result<ParsedSponsorParams, string> {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
const type = this.getParam(params, 'type');
|
const type = this.getParam(params, 'type');
|
||||||
@@ -117,7 +120,7 @@ export class SearchParamParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse pagination parameters
|
// Parse pagination parameters
|
||||||
static parsePagination(params: URLSearchParams | Record<string, string | string[] | undefined>): Result<ParsedPaginationParams, string> {
|
static parsePagination(params: URLSearchParams | Record<string, string | string[] | undefined> | undefined | null): Result<ParsedPaginationParams, string> {
|
||||||
const result: ParsedPaginationParams = {};
|
const result: ParsedPaginationParams = {};
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
@@ -157,7 +160,7 @@ export class SearchParamParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse sorting parameters
|
// Parse sorting parameters
|
||||||
static parseSorting(params: URLSearchParams | Record<string, string | string[] | undefined>): Result<ParsedSortingParams, string> {
|
static parseSorting(params: URLSearchParams | Record<string, string | string[] | undefined> | undefined | null): Result<ParsedSortingParams, string> {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
const order = this.getParam(params, 'order');
|
const order = this.getParam(params, 'order');
|
||||||
@@ -179,7 +182,7 @@ export class SearchParamParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse filter parameters
|
// Parse filter parameters
|
||||||
static parseFilters(params: URLSearchParams | Record<string, string | string[] | undefined>): Result<ParsedFilterParams, string> {
|
static parseFilters(params: URLSearchParams | Record<string, string | string[] | undefined> | undefined | null): Result<ParsedFilterParams, string> {
|
||||||
return Result.ok({
|
return Result.ok({
|
||||||
status: this.getParam(params, 'status'),
|
status: this.getParam(params, 'status'),
|
||||||
role: this.getParam(params, 'role'),
|
role: this.getParam(params, 'role'),
|
||||||
@@ -188,14 +191,14 @@ export class SearchParamParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse wizard parameters
|
// Parse wizard parameters
|
||||||
static parseWizard(params: URLSearchParams | Record<string, string | string[] | undefined>): Result<ParsedWizardParams, string> {
|
static parseWizard(params: URLSearchParams | Record<string, string | string[] | undefined> | undefined | null): Result<ParsedWizardParams, string> {
|
||||||
return Result.ok({
|
return Result.ok({
|
||||||
step: this.getParam(params, 'step'),
|
step: this.getParam(params, 'step'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse all parameters at once
|
// Parse all parameters at once
|
||||||
static parseAll(params: URLSearchParams | Record<string, string | string[] | undefined>): Result<
|
static parseAll(params: URLSearchParams | Record<string, string | string[] | undefined> | undefined | null): Result<
|
||||||
{
|
{
|
||||||
auth: ParsedAuthParams;
|
auth: ParsedAuthParams;
|
||||||
sponsor: ParsedSponsorParams;
|
sponsor: ParsedSponsorParams;
|
||||||
|
|||||||
@@ -14,11 +14,17 @@ import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorR
|
|||||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||||
|
|
||||||
// DTO types
|
// DTO types
|
||||||
import type { HomeViewData } from '@/templates/HomeTemplate';
|
|
||||||
import { Result } from '@/lib/contracts/Result';
|
import { Result } from '@/lib/contracts/Result';
|
||||||
|
import type { Service } from '@/lib/contracts/services/Service';
|
||||||
|
import type { HomeDataDTO } from '@/lib/types/dtos/HomeDataDTO';
|
||||||
|
|
||||||
export class HomeService {
|
/**
|
||||||
async getHomeData(): Promise<Result<HomeViewData, Error>> {
|
* HomeService
|
||||||
|
*
|
||||||
|
* @server-safe
|
||||||
|
*/
|
||||||
|
export class HomeService implements Service {
|
||||||
|
async getHomeData(): Promise<Result<HomeDataDTO, Error>> {
|
||||||
try {
|
try {
|
||||||
// Manual wiring: construct dependencies explicitly
|
// Manual wiring: construct dependencies explicitly
|
||||||
const baseUrl = getWebsiteApiBaseUrl();
|
const baseUrl = getWebsiteApiBaseUrl();
|
||||||
|
|||||||
20
apps/website/lib/types/dtos/HomeDataDTO.ts
Normal file
20
apps/website/lib/types/dtos/HomeDataDTO.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export interface HomeDataDTO {
|
||||||
|
isAlpha: boolean;
|
||||||
|
upcomingRaces: Array<{
|
||||||
|
id: string;
|
||||||
|
track: string;
|
||||||
|
car: string;
|
||||||
|
formattedDate: string;
|
||||||
|
}>;
|
||||||
|
topLeagues: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}>;
|
||||||
|
teams: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
logoUrl?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
@@ -3,6 +3,10 @@ module.exports = {
|
|||||||
content: [
|
content: [
|
||||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
'./templates/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
'./ui/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
'./lib/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
'./hooks/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { LandingHero } from '@/components/landing/LandingHero';
|
|
||||||
import { AlternatingSection } from '@/components/landing/AlternatingSection';
|
import { AlternatingSection } from '@/components/landing/AlternatingSection';
|
||||||
import { FeatureGrid } from '@/components/landing/FeatureGrid';
|
|
||||||
import { DiscordCTA } from '@/ui/DiscordCTA';
|
|
||||||
import { FAQ } from '@/components/landing/FAQ';
|
import { FAQ } from '@/components/landing/FAQ';
|
||||||
import { Footer } from '@/ui/Footer';
|
import { FeatureGrid } from '@/components/landing/FeatureGrid';
|
||||||
|
import { LandingHero } from '@/components/landing/LandingHero';
|
||||||
|
import { FeatureItem, ResultItem, StepItem } from '@/components/landing/LandingItems';
|
||||||
import { CareerProgressionMockup } from '@/components/mockups/CareerProgressionMockup';
|
import { CareerProgressionMockup } from '@/components/mockups/CareerProgressionMockup';
|
||||||
import { RaceHistoryMockup } from '@/components/mockups/RaceHistoryMockup';
|
|
||||||
import { CompanionAutomationMockup } from '@/components/mockups/CompanionAutomationMockup';
|
import { CompanionAutomationMockup } from '@/components/mockups/CompanionAutomationMockup';
|
||||||
|
import { RaceHistoryMockup } from '@/components/mockups/RaceHistoryMockup';
|
||||||
import { SimPlatformMockup } from '@/components/mockups/SimPlatformMockup';
|
import { SimPlatformMockup } from '@/components/mockups/SimPlatformMockup';
|
||||||
import { Card } from '@/ui/Card';
|
import { ModeGuard } from '@/components/shared/ModeGuard';
|
||||||
import { Button } from '@/ui/Button';
|
import { routes } from '@/lib/routing/RouteConfig';
|
||||||
|
import { getMediaUrl } from '@/lib/utilities/media';
|
||||||
import { Box } from '@/ui/Box';
|
import { Box } from '@/ui/Box';
|
||||||
import { Stack } from '@/ui/Stack';
|
import { Button } from '@/ui/Button';
|
||||||
import { Text } from '@/ui/Text';
|
import { Card } from '@/ui/Card';
|
||||||
|
import { Container } from '@/ui/Container';
|
||||||
|
import { DiscordCTA } from '@/ui/DiscordCTA';
|
||||||
|
import { Footer } from '@/ui/Footer';
|
||||||
|
import { Grid } from '@/ui/Grid';
|
||||||
import { Heading } from '@/ui/Heading';
|
import { Heading } from '@/ui/Heading';
|
||||||
import { Image } from '@/ui/Image';
|
import { Image } from '@/ui/Image';
|
||||||
import { Link } from '@/ui/Link';
|
import { Link } from '@/ui/Link';
|
||||||
import { Container } from '@/ui/Container';
|
import { Stack } from '@/ui/Stack';
|
||||||
import { Grid } from '@/ui/Grid';
|
|
||||||
import { Surface } from '@/ui/Surface';
|
import { Surface } from '@/ui/Surface';
|
||||||
import { getMediaUrl } from '@/lib/utilities/media';
|
import { Text } from '@/ui/Text';
|
||||||
import { routes } from '@/lib/routing/RouteConfig';
|
|
||||||
import { FeatureItem, ResultItem, StepItem } from '@/components/landing/LandingItems';
|
|
||||||
import { ModeGuard } from '@/components/shared/ModeGuard';
|
|
||||||
|
|
||||||
export interface HomeViewData {
|
export interface HomeViewData {
|
||||||
isAlpha: boolean;
|
isAlpha: boolean;
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ export interface BoxProps<T extends ElementType> {
|
|||||||
group?: boolean;
|
group?: boolean;
|
||||||
groupHoverBorderColor?: string;
|
groupHoverBorderColor?: string;
|
||||||
groupHoverTextColor?: string;
|
groupHoverTextColor?: string;
|
||||||
|
groupHoverScale?: boolean;
|
||||||
|
groupHoverOpacity?: number;
|
||||||
fontSize?: string;
|
fontSize?: string;
|
||||||
transform?: string;
|
transform?: string;
|
||||||
borderWidth?: string;
|
borderWidth?: string;
|
||||||
@@ -111,6 +113,8 @@ export interface BoxProps<T extends ElementType> {
|
|||||||
webkitMaskImage?: string;
|
webkitMaskImage?: string;
|
||||||
backgroundSize?: string;
|
backgroundSize?: string;
|
||||||
backgroundPosition?: string;
|
backgroundPosition?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
insetY?: Spacing | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponsiveValue<T> = {
|
type ResponsiveValue<T> = {
|
||||||
@@ -136,6 +140,7 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
|||||||
flexDirection,
|
flexDirection,
|
||||||
alignItems,
|
alignItems,
|
||||||
justifyContent,
|
justifyContent,
|
||||||
|
flexWrap,
|
||||||
position,
|
position,
|
||||||
top,
|
top,
|
||||||
bottom,
|
bottom,
|
||||||
@@ -183,6 +188,8 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
|||||||
group,
|
group,
|
||||||
groupHoverBorderColor,
|
groupHoverBorderColor,
|
||||||
groupHoverTextColor,
|
groupHoverTextColor,
|
||||||
|
groupHoverScale,
|
||||||
|
groupHoverOpacity,
|
||||||
fontSize,
|
fontSize,
|
||||||
transform,
|
transform,
|
||||||
borderWidth,
|
borderWidth,
|
||||||
@@ -204,6 +211,8 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
|||||||
webkitMaskImage,
|
webkitMaskImage,
|
||||||
backgroundSize,
|
backgroundSize,
|
||||||
backgroundPosition,
|
backgroundPosition,
|
||||||
|
backgroundColor,
|
||||||
|
insetY,
|
||||||
...props
|
...props
|
||||||
}: BoxProps<T> & ComponentPropsWithoutRef<T>,
|
}: BoxProps<T> & ComponentPropsWithoutRef<T>,
|
||||||
ref: ForwardedRef<HTMLElement>
|
ref: ForwardedRef<HTMLElement>
|
||||||
@@ -321,18 +330,20 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
|||||||
borderRight ? 'border-r' : '',
|
borderRight ? 'border-r' : '',
|
||||||
borderColor ? borderColor : '',
|
borderColor ? borderColor : '',
|
||||||
ring ? ring : '',
|
ring ? ring : '',
|
||||||
bg ? bg : '',
|
bg ? bg : (backgroundColor ? (backgroundColor.startsWith('bg-') ? backgroundColor : `bg-${backgroundColor}`) : ''),
|
||||||
color ? color : '',
|
color ? color : '',
|
||||||
hoverColor ? `hover:${hoverColor}` : '',
|
hoverColor ? `hover:${hoverColor}` : '',
|
||||||
shadow ? shadow : '',
|
shadow ? shadow : '',
|
||||||
flexShrink !== undefined ? `flex-shrink-${flexShrink}` : '',
|
flexShrink !== undefined ? `flex-shrink-${flexShrink}` : '',
|
||||||
flexGrow !== undefined ? `flex-grow-${flexGrow}` : '',
|
flexGrow !== undefined ? `flex-grow-${flexGrow}` : '',
|
||||||
|
flexWrap ? `flex-${flexWrap}` : '',
|
||||||
hoverBorderColor ? `hover:${hoverBorderColor}` : '',
|
hoverBorderColor ? `hover:${hoverBorderColor}` : '',
|
||||||
hoverTextColor ? `hover:${hoverTextColor}` : '',
|
hoverTextColor ? `hover:${hoverTextColor}` : '',
|
||||||
hoverBg ? `hover:${hoverBg}` : '',
|
hoverBg ? `hover:${hoverBg}` : '',
|
||||||
transition ? 'transition-all' : '',
|
transition ? 'transition-all' : '',
|
||||||
lineClamp ? `line-clamp-${lineClamp}` : '',
|
lineClamp ? `line-clamp-${lineClamp}` : '',
|
||||||
inset ? `inset-${inset}` : '',
|
inset ? `inset-${inset}` : '',
|
||||||
|
insetY !== undefined && spacingMap[insetY as string | number] ? `inset-y-${spacingMap[insetY as string | number]}` : '',
|
||||||
bgOpacity !== undefined ? `bg-opacity-${bgOpacity * 100}` : '',
|
bgOpacity !== undefined ? `bg-opacity-${bgOpacity * 100}` : '',
|
||||||
opacity !== undefined ? `opacity-${opacity * 100}` : '',
|
opacity !== undefined ? `opacity-${opacity * 100}` : '',
|
||||||
blur ? `blur-${blur}` : '',
|
blur ? `blur-${blur}` : '',
|
||||||
@@ -343,6 +354,8 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
|||||||
group ? 'group' : '',
|
group ? 'group' : '',
|
||||||
groupHoverBorderColor ? `group-hover:border-${groupHoverBorderColor}` : '',
|
groupHoverBorderColor ? `group-hover:border-${groupHoverBorderColor}` : '',
|
||||||
groupHoverTextColor ? `group-hover:text-${groupHoverTextColor}` : '',
|
groupHoverTextColor ? `group-hover:text-${groupHoverTextColor}` : '',
|
||||||
|
groupHoverScale ? 'group-hover:scale-[1.02]' : '',
|
||||||
|
groupHoverOpacity !== undefined ? `group-hover:opacity-${groupHoverOpacity * 100}` : '',
|
||||||
getResponsiveClasses('', display),
|
getResponsiveClasses('', display),
|
||||||
getFlexDirectionClass(flexDirection),
|
getFlexDirectionClass(flexDirection),
|
||||||
getAlignItemsClass(alignItems),
|
getAlignItemsClass(alignItems),
|
||||||
@@ -390,6 +403,7 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
|||||||
...(bottom !== undefined && !spacingMap[bottom as string | number] ? { bottom } : {}),
|
...(bottom !== undefined && !spacingMap[bottom as string | number] ? { bottom } : {}),
|
||||||
...(left !== undefined && !spacingMap[left as string | number] ? { left } : {}),
|
...(left !== undefined && !spacingMap[left as string | number] ? { left } : {}),
|
||||||
...(right !== undefined && !spacingMap[right as string | number] ? { right } : {}),
|
...(right !== undefined && !spacingMap[right as string | number] ? { right } : {}),
|
||||||
|
...(insetY !== undefined && !spacingMap[insetY as string | number] ? { top: insetY, bottom: insetY } : {}),
|
||||||
...(hideScrollbar ? { scrollbarWidth: 'none', msOverflowStyle: 'none', '&::-webkit-scrollbar': { display: 'none' } } : {}),
|
...(hideScrollbar ? { scrollbarWidth: 'none', msOverflowStyle: 'none', '&::-webkit-scrollbar': { display: 'none' } } : {}),
|
||||||
...((props as Record<string, unknown>).style as object || {})
|
...((props as Record<string, unknown>).style as object || {})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { ReactNode, ElementType, ComponentPropsWithoutRef } from 'react';
|
import React, { ReactNode, ElementType, ComponentPropsWithoutRef } from 'react';
|
||||||
import { BoxProps } from './Box';
|
import { Box, BoxProps } from './Box';
|
||||||
|
|
||||||
type Spacing = 0 | 0.5 | 1 | 1.5 | 2 | 2.5 | 3 | 3.5 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 64 | 72 | 80 | 96;
|
type Spacing = 0 | 0.5 | 1 | 1.5 | 2 | 2.5 | 3 | 3.5 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 64 | 72 | 80 | 96;
|
||||||
|
|
||||||
@@ -194,5 +194,5 @@ export function Text<T extends ElementType = 'span'>({
|
|||||||
...style
|
...style
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Tag className={classes} style={combinedStyle} {...props}>{children}</Tag>;
|
return <Box as={Tag} className={classes} style={combinedStyle} {...props}>{children}</Box>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ services:
|
|||||||
- NEXT_TELEMETRY_DISABLED=1
|
- NEXT_TELEMETRY_DISABLED=1
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
- API_BASE_URL=http://api:3000
|
- API_BASE_URL=http://api:3000
|
||||||
|
- DOCKER=true
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
Reference in New Issue
Block a user