Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<app-cart-count-controls
(decrement)="remove.emit()"
(increment)="add.emit()"
[productName]="product().title"
[productName]="product().name"
[count]="product().orderedCount"
[available]="product().count"
[available]="product().inStock ? 999 : 0"
></app-cart-count-controls>
</div>
}
Expand All @@ -16,7 +16,7 @@
[class.col-md-9]="hideControls()"
class="col-12 col-md-6"
>
<h3 class="font-weight-bold mb-1">{{ product().title }}</h3>
<h3 class="font-weight-bold mb-1">{{ product().name }}</h3>
<p class="text-muted">{{ product().description }}</p>
</div>
<div class="col-12 col-md-3">
Expand Down
20 changes: 14 additions & 6 deletions FE/src/app/products/product-item/product-item.component.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
<mat-card>
<div mat-card-image class="img-container">
<div class="img-container__inner">
<img src="https://source.unsplash.com/random?sig={{ index() }}" alt="" />
<img [src]="product().imageUrl" [alt]="product().name" />
</div>
</div>

<mat-card-header>
<mat-card-title>
{{ product().title }}
{{ product().name }}
<span class="artist">{{ product().artist }}</span>
</mat-card-title>
<mat-card-subtitle>
{{ product().category }} • {{ product().genre }} • {{ product().year }}
</mat-card-subtitle>
</mat-card-header>

<mat-card-content>
<p>{{ product().price | number: "1.2-2" | currency }}</p>
<p class="description">{{ product().description }}</p>
<p class="price">{{ product().price | currency }}</p>
@if (!product().inStock) {
<p class="out-of-stock">Out of Stock</p>
}
</mat-card-content>

<mat-card-actions>
Expand All @@ -22,7 +30,7 @@
#cartBtn
(click)="add()"
color="accent"
matTooltip="Add {{ product().title }} to cart"
matTooltip="Add {{ product().name }} to cart"
mat-icon-button
>
<mat-icon>shopping_cart</mat-icon>
Expand All @@ -32,9 +40,9 @@
#controls="countControls"
(increment)="add()"
(decrement)="remove()"
[productName]="product().title"
[productName]="product().name"
[count]="countInCart()"
[available]="product().count"
[available]="product().inStock ? 999 : 0"
></app-cart-count-controls>
}
</mat-card-actions>
Expand Down
67 changes: 67 additions & 0 deletions FE/src/app/products/product-item/product-item.component.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// Make all cards the same height
mat-card {
height: 100%;
display: flex;
flex-direction: column;
}

.img-container {
padding-top: 90%;
position: relative;
Expand All @@ -17,3 +24,63 @@
object-position: center center;
}
}

// Card header section
mat-card-header {
flex-shrink: 0;
}

.artist {
display: block;
font-size: 0.9em;
font-weight: normal;
color: rgba(0, 0, 0, 0.6);
margin-top: 4px;
}

mat-card-subtitle {
font-size: 0.85em;
color: rgba(0, 0, 0, 0.5);
margin-top: 4px;
}

// Card content section - this will expand to fill available space
mat-card-content {
flex: 1;
display: flex;
flex-direction: column;
}

.description {
font-size: 0.9em;
line-height: 1.4;
margin-bottom: 8px;
color: rgba(0, 0, 0, 0.7);
// Limit to 2 rows with ellipsis
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2; // Standard property for compatibility
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
max-height: 2.8em; // 2 lines * 1.4 line-height
}

.price {
font-size: 1.1em;
font-weight: 600;
color: #2c3e50;
margin: auto 0 8px 0; // Push to bottom of content area
}

.out-of-stock {
color: #e74c3c;
font-weight: 600;
margin: 0;
font-size: 0.9em;
}

// Card actions section - always at bottom
mat-card-actions {
flex-shrink: 0;
}
2 changes: 2 additions & 0 deletions FE/src/app/products/product-item/product-item.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
MatCardContent,
MatCardHeader,
MatCardImage,
MatCardSubtitle,
MatCardTitle,
} from '@angular/material/card';

Expand All @@ -35,6 +36,7 @@ import {
MatCardImage,
MatCardHeader,
MatCardTitle,
MatCardSubtitle,
MatCardContent,
MatCardActions,
MatIconButton,
Expand Down
12 changes: 8 additions & 4 deletions FE/src/app/products/product.interface.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
export interface Product {
/** Available count */
count: number;
description: string;
id: string;
name: string;
artist: string;
description: string;
price: number;
title: string;
category: string;
genre: string;
year: number;
inStock: boolean;
imageUrl: string;
}

export interface ProductCheckout extends Product {
Expand Down
6 changes: 3 additions & 3 deletions FE/src/app/products/products.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ export class ProductsService extends ApiService {
}

getProducts(): Observable<Product[]> {
if (!this.endpointEnabled('bff')) {
if (!this.endpointEnabled('product')) {
console.warn(
'Endpoint "bff" is disabled. To enable change your environment.ts config'
'Endpoint "product" is disabled. To enable change your environment.ts config'
);
return this.http.get<Product[]>('/assets/products.json');
}

const url = this.getUrl('bff', 'products');
const url = this.getUrl('product', 'products');
return this.http.get<Product[]>(url);
}

Expand Down
4 changes: 2 additions & 2 deletions FE/src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { Config } from './config.interface';
export const environment: Config = {
production: true,
apiEndpoints: {
product: 'https://.execute-api.eu-west-1.amazonaws.com/dev',
product: 'https://uf4ds80g46.execute-api.us-east-1.amazonaws.com/prod',
order: 'https://.execute-api.eu-west-1.amazonaws.com/dev',
import: 'https://.execute-api.eu-west-1.amazonaws.com/dev',
bff: 'https://.execute-api.eu-west-1.amazonaws.com/dev',
cart: 'https://.execute-api.eu-west-1.amazonaws.com/dev',
},
apiEndpointsEnabled: {
product: false,
product: true,
order: false,
import: false,
bff: false,
Expand Down
4 changes: 2 additions & 2 deletions FE/src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { Config } from './config.interface';
export const environment: Config = {
production: false,
apiEndpoints: {
product: 'https://.execute-api.eu-west-1.amazonaws.com/dev',
product: 'https://uf4ds80g46.execute-api.us-east-1.amazonaws.com/prod',
order: 'https://.execute-api.eu-west-1.amazonaws.com/dev',
import: 'https://.execute-api.eu-west-1.amazonaws.com/dev',
bff: 'https://.execute-api.eu-west-1.amazonaws.com/dev',
cart: 'https://.execute-api.eu-west-1.amazonaws.com/dev',
},
apiEndpointsEnabled: {
product: false,
product: true,
order: false,
import: false,
bff: false,
Expand Down
9 changes: 8 additions & 1 deletion infra/bin/infra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { DeployWebAppStack } from '../lib/deploy-web-app-stack';
import { HelloLambdaStack } from '../lib/hello-lambda/hello-lambda-stack';
import { ProductServiceStack } from '../lib/product-service/product-service-stack';

const app = new cdk.App();
new DeployWebAppStack(app, 'DeployWebAppStack', {
Expand All @@ -18,4 +20,9 @@ new DeployWebAppStack(app, 'DeployWebAppStack', {
// env: { account: '123456789012', region: 'us-east-1' },

/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});

});

new HelloLambdaStack(app, 'HelloLambdaStack', {});

new ProductServiceStack(app, 'ProductServiceStack', {});
5 changes: 5 additions & 0 deletions infra/lib/hello-lambda/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export async function main(event: { message: any; }) {
return {
message: `SUCCESS with message ${event.message} 🎉`
};
}
49 changes: 49 additions & 0 deletions infra/lib/hello-lambda/hello-lambda-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Filename: hello-lambda-stack.ts
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as cdk from 'aws-cdk-lib';
import * as path from 'path';
import { Construct } from 'constructs';

export class HelloLambdaStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const lambdaFunction = new lambda.Function(this, 'lambda-function', {
runtime: lambda.Runtime.NODEJS_20_X,
memorySize: 1024,
timeout: cdk.Duration.seconds(5),
handler: 'handler.main',
code: lambda.Code.fromAsset(path.join(__dirname, './')),
});

const api = new apigateway.RestApi(this, "my-api", {
restApiName: "My API Gateway",
description: "This API serves the Lambda functions."
});

// Filename: hello-lambda-stack.ts
const helloFromLambdaIntegration = new apigateway.LambdaIntegration(lambdaFunction, {
requestTemplates: {
"application/json":
`{ "message": "$input.params('message')" }` // Map the query param message
},
integrationResponses: [
{ statusCode: '200' },
],
proxy: false,
});

// Create a resource /hello and GET request under it
const helloResource = api.root.addResource("hello");
// On this resource attach a GET method which pass reuest to our Lambda function
helloResource.addMethod('GET', helloFromLambdaIntegration, {
methodResponses: [{ statusCode: '200' }]
});

helloResource.addCorsPreflight({
allowOrigins: ['https://your-frontend-url.com'],
allowMethods: ['GET'],
});
}
}
Loading