wip
This commit is contained in:
@@ -12,6 +12,7 @@ export interface LiveryDecalProps {
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
rotation: number; // Degrees, 0-360
|
||||
zIndex: number;
|
||||
type: DecalType;
|
||||
}
|
||||
@@ -23,6 +24,7 @@ export class LiveryDecal {
|
||||
readonly y: number;
|
||||
readonly width: number;
|
||||
readonly height: number;
|
||||
readonly rotation: number;
|
||||
readonly zIndex: number;
|
||||
readonly type: DecalType;
|
||||
|
||||
@@ -33,13 +35,18 @@ export class LiveryDecal {
|
||||
this.y = props.y;
|
||||
this.width = props.width;
|
||||
this.height = props.height;
|
||||
this.rotation = props.rotation;
|
||||
this.zIndex = props.zIndex;
|
||||
this.type = props.type;
|
||||
}
|
||||
|
||||
static create(props: LiveryDecalProps): LiveryDecal {
|
||||
this.validate(props);
|
||||
return new LiveryDecal(props);
|
||||
static create(props: Omit<LiveryDecalProps, 'rotation'> & { rotation?: number }): LiveryDecal {
|
||||
const propsWithRotation = {
|
||||
...props,
|
||||
rotation: props.rotation ?? 0,
|
||||
};
|
||||
this.validate(propsWithRotation);
|
||||
return new LiveryDecal(propsWithRotation);
|
||||
}
|
||||
|
||||
private static validate(props: LiveryDecalProps): void {
|
||||
@@ -71,6 +78,10 @@ export class LiveryDecal {
|
||||
throw new Error('LiveryDecal zIndex must be a non-negative integer');
|
||||
}
|
||||
|
||||
if (props.rotation < 0 || props.rotation > 360) {
|
||||
throw new Error('LiveryDecal rotation must be between 0 and 360 degrees');
|
||||
}
|
||||
|
||||
if (!props.type) {
|
||||
throw new Error('LiveryDecal type is required');
|
||||
}
|
||||
@@ -108,6 +119,25 @@ export class LiveryDecal {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate decal
|
||||
*/
|
||||
rotate(rotation: number): LiveryDecal {
|
||||
// Normalize rotation to 0-360 range
|
||||
const normalizedRotation = ((rotation % 360) + 360) % 360;
|
||||
return LiveryDecal.create({
|
||||
...this,
|
||||
rotation: normalizedRotation,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS transform string for rendering
|
||||
*/
|
||||
getCssTransform(): string {
|
||||
return `rotate(${this.rotation}deg)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this decal overlaps with another
|
||||
*/
|
||||
|
||||
208
packages/racing/domain/value-objects/SponsorshipPricing.ts
Normal file
208
packages/racing/domain/value-objects/SponsorshipPricing.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Value Object: SponsorshipPricing
|
||||
*
|
||||
* Represents the sponsorship slot configuration and pricing for any sponsorable entity.
|
||||
* Used by drivers, teams, races, and leagues to define their sponsorship offerings.
|
||||
*/
|
||||
|
||||
import { Money } from './Money';
|
||||
|
||||
export interface SponsorshipSlotConfig {
|
||||
tier: 'main' | 'secondary';
|
||||
price: Money;
|
||||
benefits: string[];
|
||||
available: boolean;
|
||||
maxSlots: number; // How many sponsors of this tier can exist (1 for main, 2 for secondary typically)
|
||||
}
|
||||
|
||||
export interface SponsorshipPricingProps {
|
||||
mainSlot?: SponsorshipSlotConfig;
|
||||
secondarySlots?: SponsorshipSlotConfig;
|
||||
acceptingApplications: boolean;
|
||||
customRequirements?: string;
|
||||
}
|
||||
|
||||
export class SponsorshipPricing {
|
||||
readonly mainSlot?: SponsorshipSlotConfig;
|
||||
readonly secondarySlots?: SponsorshipSlotConfig;
|
||||
readonly acceptingApplications: boolean;
|
||||
readonly customRequirements?: string;
|
||||
|
||||
private constructor(props: SponsorshipPricingProps) {
|
||||
this.mainSlot = props.mainSlot;
|
||||
this.secondarySlots = props.secondarySlots;
|
||||
this.acceptingApplications = props.acceptingApplications;
|
||||
this.customRequirements = props.customRequirements;
|
||||
}
|
||||
|
||||
static create(props: Partial<SponsorshipPricingProps> = {}): SponsorshipPricing {
|
||||
return new SponsorshipPricing({
|
||||
mainSlot: props.mainSlot,
|
||||
secondarySlots: props.secondarySlots,
|
||||
acceptingApplications: props.acceptingApplications ?? true,
|
||||
customRequirements: props.customRequirements,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default pricing for a driver
|
||||
*/
|
||||
static defaultDriver(): SponsorshipPricing {
|
||||
return new SponsorshipPricing({
|
||||
mainSlot: {
|
||||
tier: 'main',
|
||||
price: Money.create(200, 'USD'),
|
||||
benefits: ['Suit logo', 'Helmet branding', 'Social mentions'],
|
||||
available: true,
|
||||
maxSlots: 1,
|
||||
},
|
||||
acceptingApplications: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default pricing for a team
|
||||
*/
|
||||
static defaultTeam(): SponsorshipPricing {
|
||||
return new SponsorshipPricing({
|
||||
mainSlot: {
|
||||
tier: 'main',
|
||||
price: Money.create(500, 'USD'),
|
||||
benefits: ['Team name suffix', 'Car livery', 'All driver suits'],
|
||||
available: true,
|
||||
maxSlots: 1,
|
||||
},
|
||||
secondarySlots: {
|
||||
tier: 'secondary',
|
||||
price: Money.create(250, 'USD'),
|
||||
benefits: ['Team page logo', 'Minor livery placement'],
|
||||
available: true,
|
||||
maxSlots: 2,
|
||||
},
|
||||
acceptingApplications: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default pricing for a race
|
||||
*/
|
||||
static defaultRace(): SponsorshipPricing {
|
||||
return new SponsorshipPricing({
|
||||
mainSlot: {
|
||||
tier: 'main',
|
||||
price: Money.create(300, 'USD'),
|
||||
benefits: ['Race title sponsor', 'Stream overlay', 'Results banner'],
|
||||
available: true,
|
||||
maxSlots: 1,
|
||||
},
|
||||
acceptingApplications: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default pricing for a league/season
|
||||
*/
|
||||
static defaultLeague(): SponsorshipPricing {
|
||||
return new SponsorshipPricing({
|
||||
mainSlot: {
|
||||
tier: 'main',
|
||||
price: Money.create(800, 'USD'),
|
||||
benefits: ['Hood placement', 'League banner', 'Prominent logo', 'League page URL'],
|
||||
available: true,
|
||||
maxSlots: 1,
|
||||
},
|
||||
secondarySlots: {
|
||||
tier: 'secondary',
|
||||
price: Money.create(250, 'USD'),
|
||||
benefits: ['Side logo placement', 'League page listing'],
|
||||
available: true,
|
||||
maxSlots: 2,
|
||||
},
|
||||
acceptingApplications: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific tier is available
|
||||
*/
|
||||
isSlotAvailable(tier: 'main' | 'secondary'): boolean {
|
||||
if (tier === 'main') {
|
||||
return !!this.mainSlot?.available;
|
||||
}
|
||||
return !!this.secondarySlots?.available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get price for a specific tier
|
||||
*/
|
||||
getPrice(tier: 'main' | 'secondary'): Money | null {
|
||||
if (tier === 'main') {
|
||||
return this.mainSlot?.price ?? null;
|
||||
}
|
||||
return this.secondarySlots?.price ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get benefits for a specific tier
|
||||
*/
|
||||
getBenefits(tier: 'main' | 'secondary'): string[] {
|
||||
if (tier === 'main') {
|
||||
return this.mainSlot?.benefits ?? [];
|
||||
}
|
||||
return this.secondarySlots?.benefits ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update main slot pricing
|
||||
*/
|
||||
updateMainSlot(config: Partial<SponsorshipSlotConfig>): SponsorshipPricing {
|
||||
const currentMain = this.mainSlot ?? {
|
||||
tier: 'main' as const,
|
||||
price: Money.create(0, 'USD'),
|
||||
benefits: [],
|
||||
available: true,
|
||||
maxSlots: 1,
|
||||
};
|
||||
|
||||
return new SponsorshipPricing({
|
||||
...this,
|
||||
mainSlot: {
|
||||
...currentMain,
|
||||
...config,
|
||||
tier: 'main',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update secondary slot pricing
|
||||
*/
|
||||
updateSecondarySlot(config: Partial<SponsorshipSlotConfig>): SponsorshipPricing {
|
||||
const currentSecondary = this.secondarySlots ?? {
|
||||
tier: 'secondary' as const,
|
||||
price: Money.create(0, 'USD'),
|
||||
benefits: [],
|
||||
available: true,
|
||||
maxSlots: 2,
|
||||
};
|
||||
|
||||
return new SponsorshipPricing({
|
||||
...this,
|
||||
secondarySlots: {
|
||||
...currentSecondary,
|
||||
...config,
|
||||
tier: 'secondary',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable accepting applications
|
||||
*/
|
||||
setAcceptingApplications(accepting: boolean): SponsorshipPricing {
|
||||
return new SponsorshipPricing({
|
||||
...this,
|
||||
acceptingApplications: accepting,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user