Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions apps/api/src/modules/Product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,30 @@ export class ProductModule {
return this.db.Get<ProductType>('Products', id);
}

/**
* Batch-load products by id, scoped to a single platform account.
* Used by the expansion engine.
*/
async BatchGet(
ids: string[],
platformAccount: string
): Promise<Map<string, ProductType>> {
if (ids.length === 0) return new Map();
const products = await this.db.Query<ProductType>({
collection: 'Products',
method: 'READ',
parameters: [
{ key: 'id', operator: QueryOperators['in'], value: ids },
{
key: 'platform_account',
operator: QueryOperators['=='],
value: platformAccount,
},
],
});
return new Map(products.map((product) => [product.id, product]));
}

/**
* Update a product.
* Emits an 'product.updated' event if EventService is configured.
Expand Down
17 changes: 13 additions & 4 deletions apps/api/src/routes/prices.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@ import {
ParseCreatedFilter,
ParseOptionalQueryBoolean,
} from '../utils/ListHelper';
import { ApplyExpand, RegisterExpansions } from '../utils/Expand';

const router = express.Router();

const eventService = new EventService(db);
const productModule = new ProductModule(db, eventService); //Pass into price module to enable creating products.
const priceModule = new PriceModule(db, eventService, productModule);

RegisterExpansions('price', {
product: {
sourcePath: 'product',
targetObject: 'product',
BatchLoad: (ids, ctx) => productModule.BatchGet(ids, ctx.platformAccount),
},
});

/**
* POST /v1/prices
* Create a new price.
Expand All @@ -46,7 +55,7 @@ router.post(
priceId: price.id,
});

res.status(201).json(price);
res.status(201).json(await ApplyExpand(req, price));
})
);
export default router;
Expand Down Expand Up @@ -95,7 +104,7 @@ router.post(
priceId: updatedPrice.id,
});

res.json(updatedPrice);
res.json(await ApplyExpand(req, updatedPrice));
})
);

Expand Down Expand Up @@ -129,7 +138,7 @@ router.get(
);
}

res.json(price);
res.json(await ApplyExpand(req, price));
})
);

Expand Down Expand Up @@ -189,6 +198,6 @@ router.get(
hasMore: result.has_more,
});

res.json(result);
res.json(await ApplyExpand(req, result));
})
);
6 changes: 5 additions & 1 deletion apps/web/src/app/data/services/price.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class PriceService {

async UpdatePrice(priceId: string, data: UpdatePriceInput): Promise<Price> {
this.loading.set(true);
data.expand = ['default_price'];
try {
const price = await this.api.Call<Price>(
'POST',
Expand All @@ -47,7 +48,10 @@ export class PriceService {
async GetPrice(priceId: string): Promise<Price> {
this.loading.set(true);
try {
const price = await this.api.Call<Price>('GET', `prices/${priceId}`);
const price = await this.api.Call<Price>(
'GET',
`prices/${priceId}?expand=product`
);
return price;
} finally {
this.loading.set(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ export class ProductActionsService {
}

async SetDefaultPrice(price: Price): Promise<void> {
const product = await this.productService.UpdateProduct(price.product, {
const productId = price.product as string;
const product = await this.productService.UpdateProduct(productId, {
default_price: price.id,
});
this.CreateEvent({ type: 'updated', product: product });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ <h4>This price has been archived</h4>
@if(price.nickname){
<h1 class="mb-zero">{{ price.nickname }}</h1>
} @else {
<h1 class="mb-zero">Price for {{ price.product }}</h1>
<h1 class="mb-zero">Price for {{ productName() }}</h1>
} @if(!price.active){
<span class="chip grey-chip">Archived</span>
}
Expand Down Expand Up @@ -81,7 +81,7 @@ <h1 class="mb-zero">Price for {{ price.product }}</h1>
(keydown.enter)="GoToProduct()"
(keydown.space)="GoToProduct()"
tabindex="0"
>{{ price.product }}</a
>{{ productName() }}</a
>
</div>
<div class="vertical-line"></div>
Expand Down Expand Up @@ -214,7 +214,13 @@ <h2 class="mb-zero">Pricing</h2>
</tr>
<tr>
<td class="dimmed-extra">Default price</td>
<td>-</td>
<td>
@if(isDefaultPrice()){
<span>Yes</span>
} @else {
<span>No</span>
}
</td>
</tr>
</tbody>
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
WritableSignal,
} from '@angular/core';
import { DecimalPipe, UpperCasePipe } from '@angular/common';
import type { Price } from '@zoneless/shared-types';
import type { Price, Product } from '@zoneless/shared-types';
import { PriceService } from '../../../../../data';
import { PriceActionsService } from '../../services/price-actions.service';
import { PriceActionsHostComponent } from '../../components/price-actions-host/price-actions-host.component';
Expand Down Expand Up @@ -49,6 +49,10 @@ export class PriceDetailComponent {
archivedBannedOpen: WritableSignal<boolean> = signal(true);

price: WritableSignal<Price | null> = signal(null);
relatedProduct: WritableSignal<Product | null> = signal(null);
productName: WritableSignal<string | null> = signal(null);
isDefaultPrice: WritableSignal<boolean> = signal(false);

private sub?: Subscription;

popupMenuActions: PopupMenuAction[] = [
Expand All @@ -68,6 +72,7 @@ export class PriceDetailComponent {
const id = this.route.snapshot.paramMap.get('priceId');
if (!id) return;
await this.LoadPrice(id);
this.ConfigureProductDetails(this.price() as Price);
this.metaService.SetMetaTitle(
this.price()?.nickname || this.price()?.id || 'Price'
);
Expand All @@ -85,6 +90,23 @@ export class PriceDetailComponent {
});
}

ConfigureProductDetails(price: Price): void {
if (
price.product &&
typeof price.product === 'object' &&
'name' in price.product
) {
const product = price.product as Product;
this.relatedProduct.set(product);
this.productName.set(product.name);
this.isDefaultPrice.set(product.default_price === price.id);
} else if (typeof price.product === 'string') {
this.productName.set(price.product);
} else {
this.productName.set(null);
}
}

private async LoadPrice(id: string): Promise<void> {
this.loading.set(true);
try {
Expand All @@ -100,7 +122,7 @@ export class PriceDetailComponent {
}

GoToProduct(): void {
this.router.navigate(['/account/products', this.price()?.product]);
this.router.navigate(['/account/products', this.relatedProduct()?.id]);
}

OnEditMetadata(): void {
Expand Down
Loading
Loading