Skip to content

Commit

Permalink
Merge pull request #971 from cardstack/cardmaker-episode-4
Browse files Browse the repository at this point in the history
Improvements from recording session of CardMaker Episode 4
  • Loading branch information
lukemelia authored Jan 17, 2024
2 parents 9e87a1e + 29fe455 commit e280f96
Show file tree
Hide file tree
Showing 10 changed files with 892 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@
},
"products.6": {
"links": {
"self": "../Product/03b3e910-a7ea-4881-b2cc-a1bb738530d9"
"self": "../Product/518dbfc8-a9fa-4403-8422-885edb64063f"
}
},
"products.7": {
"links": {
"self": "../Product/518dbfc8-a9fa-4403-8422-885edb64063f"
"self": "../Product/13e1dc95-e84d-4ddd-ad17-fa6b3c59cca5"
}
},
"products.8": {
"links": {
"self": "../Product/13e1dc95-e84d-4ddd-ad17-fa6b3c59cca5"
"self": "../VideoProduct/9d37cb15-f63a-4eb1-ba1b-f209a12fe1c8"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"data": {
"type": "card",
"attributes": {
"videoUrl": "https://v.etsystatic.com/video/upload/s--nMgoUlxI--/ac_none,c_crop,du_15,h_960,q_auto:good,w_720,x_0,y_0/IMG_2082_dnw70f",
"images": [
"https://i.etsystatic.com/8595526/r/il/b3b96c/3064849416/il_1588xN.3064849416_r41c.jpg",
"https://i.etsystatic.com/8595526/r/il/19a557/3064848658/il_1588xN.3064848658_m2t5.jpg",
"https://i.etsystatic.com/8595526/r/il/ac2b0b/3064860134/il_1588xN.3064860134_mm80.jpg",
"https://i.etsystatic.com/8595526/r/il/0e8ff6/3112592903/il_1588xN.3112592903_hq05.jpg",
"https://i.etsystatic.com/8595526/r/il/370ede/3064859840/il_1588xN.3064859840_rft0.jpg"
],
"unitPriceCents": 10277,
"usShippingCostCents": 0,
"leadTimeDays": 14,
"deliveryWindowDays": 19,
"isReturnable": false,
"details": "This listing is a special deal. It's a HEAVILY DISCOUNTED package of the 2 busts that I make and sell separately – Marcus Aurelius and Seneca.\n\nThese busts look and feel great. Their rustic appearance is a joy to look at. The stone cold concrete is mesmerising to the touch!\n\nIn the past, my customers were buying these separately for a full price, but now you can get these two together and pay around 25% less.",
"title": "MARCUS + SENECA Premium Concrete Busts | Stoic Set",
"description": null
},
"relationships": {
"seller": {
"links": {
"self": "../Seller/2e039829-9d63-4e06-a257-0986614d9242"
}
}
},
"meta": {
"adoptsFrom": {
"module": "../product-with-video",
"name": "ProductWithVideo"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"data": {
"type": "card",
"attributes": {
"videoUrl": "https://v.etsystatic.com/video/upload/s--nMgoUlxI--/ac_none,c_crop,du_15,h_960,q_auto:good,w_720,x_0,y_0/IMG_2082_dnw70f",
"images": [
"https://i.etsystatic.com/8595526/r/il/b3b96c/3064849416/il_1588xN.3064849416_r41c.jpg",
"https://i.etsystatic.com/8595526/r/il/19a557/3064848658/il_1588xN.3064848658_m2t5.jpg",
"https://i.etsystatic.com/8595526/r/il/ac2b0b/3064860134/il_1588xN.3064860134_mm80.jpg",
"https://i.etsystatic.com/8595526/r/il/0e8ff6/3112592903/il_1588xN.3112592903_hq05.jpg",
"https://i.etsystatic.com/8595526/r/il/370ede/3064859840/il_1588xN.3064859840_rft0.jpg"
],
"unitPriceCents": 10277,
"usShippingCostCents": 0,
"leadTimeDays": 14,
"deliveryWindowDays": 19,
"isReturnable": false,
"details": "This listing is a special deal. It's a HEAVILY DISCOUNTED package of the 2 busts that I make and sell separately – Marcus Aurelius and Seneca.\n\nThese busts look and feel great. Their rustic appearance is a joy to look at. The stone cold concrete is mesmerising to the touch!\n\nIn the past, my customers were buying these separately for a full price, but now you can get these two together and pay around 25% less.",
"ratingsSummary": {
"average": 4.99,
"count": 385
},
"title": "MARCUS + SENECA Premium Concrete Busts | Stoic Set",
"description": null
},
"relationships": {
"seller": {
"links": {
"self": "../Seller/2e039829-9d63-4e06-a257-0986614d9242"
}
}
},
"meta": {
"adoptsFrom": {
"module": "../product-with-video-and-ratings",
"name": "ProductWithVideoAndRatings"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"data": {
"type": "card",
"attributes": {
"videoUrl": null,
"images": [],
"unitPriceCents": null,
"usShippingCostCents": null,
"leadTimeDays": null,
"deliveryWindowDays": null,
"isReturnable": null,
"details": null,
"title": null,
"description": null
},
"relationships": {
"seller": {
"links": {
"self": null
}
}
},
"meta": {
"adoptsFrom": {
"module": "../video-product",
"name": "VideoProduct"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"data": {
"type": "card",
"attributes": {
"videoUrl": "https://v.etsystatic.com/video/upload/s--nMgoUlxI--/ac_none,c_crop,du_15,h_960,q_auto:good,w_720,x_0,y_0/IMG_2082_dnw70f",
"images": [
"https://i.etsystatic.com/8595526/r/il/b3b96c/3064849416/il_1588xN.3064849416_r41c.jpg",
"https://i.etsystatic.com/8595526/r/il/19a557/3064848658/il_1588xN.3064848658_m2t5.jpg",
"https://i.etsystatic.com/8595526/r/il/ac2b0b/3064860134/il_1588xN.3064860134_mm80.jpg",
"https://i.etsystatic.com/8595526/r/il/0e8ff6/3112592903/il_1588xN.3112592903_hq05.jpg",
"https://i.etsystatic.com/8595526/r/il/370ede/3064859840/il_1588xN.3064859840_rft0.jpg"
],
"unitPriceCents": 10277,
"usShippingCostCents": 0,
"leadTimeDays": 14,
"deliveryWindowDays": 19,
"isReturnable": false,
"details": "Thislisting is a special deal. It's a HEAVILY DISCOUNTED package of the 2 busts that I make and sell separately – Marcus Aurelius and Seneca.\n\nThese busts look and feel great. Their rustic appearance is a joy to look at. The stone cold concrete is mesmerising to the touch!\n\nIn the past, my customers were buying these separately for a full price, but now you can get these two together and pay around 25% less.",
"title": "MARCUS + SENECA Premium Concrete Busts | Stoic Set",
"description": null
},
"relationships": {
"seller": {
"links": {
"self": "../Seller/2e039829-9d63-4e06-a257-0986614d9242"
}
}
},
"meta": {
"adoptsFrom": {
"module": "../video-product",
"name": "VideoProduct"
}
}
}
}
230 changes: 230 additions & 0 deletions packages/drafts-realm/product-list-with-filter.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import {
CardDef,
field,
linksToMany,
} from 'https://cardstack.com/base/card-api';
import { Component } from 'https://cardstack.com/base/card-api';
import {
Product as ProductCard,
formatUsd,
EmbeddedProductComponent,
} from './product';
import GlimmerComponent from '@glimmer/component';
import { fn } from '@ember/helper';
import { on } from '@ember/modifier';
import { BoxelInput } from '@cardstack/boxel-ui/components';
// @ts-ignore TS1206: Decorators are not valid here.
import { action } from '@ember/object';
// @ts-ignore TS1206: Decorators are not valid here.
import { tracked } from '@glimmer/tracking';

interface FeaturedProductComponentSignature {
Args: {
model: ProductCard | undefined;
viewProduct: (arg0: ProductCard | undefined) => void;
};
}

class FeaturedProductComponent extends GlimmerComponent<FeaturedProductComponentSignature> {
<template>
<div class='product'>
<img
{{on 'click' (fn @viewProduct @model)}}
src={{@model.thumbnailURL}}
alt={{@model.title}}
/>
<div>
<div class='seller'>
{{@model.seller.title}}
</div>
<div class='title'>
{{@model.title}}
</div>
<div class='price'>
{{formatUsd @model.unitPriceCents}}
</div>
<button {{on 'click' (fn @viewProduct @model)}}>Shop this item</button>
</div>
</div>
<style>
.product {
display: grid;
grid-template-columns: 1.5fr 2.5fr;
grid-gap: var(--boxel-sp);
}
img {
border-radius: 10px;
display: block;
max-width: 100%;
aspect-ratio: 1.7;
object-fit: cover;
}
.seller {
margin-top: 6px;
font-size: 14px;
}
.title {
margin-top: 6px;
font-weight: 500;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.price {
color: green;
}
.title,
.price {
font-weight: 600;
font-size: 18px;
line-height: 24px;
}
button {
margin-top: 10px;
border-radius: 20px;
background: black;
color: white;
font-weight: 500;
font-size: 14px;
padding: 7px 24px;
border: 0;
}
</style>
</template>
}

export class ProductList extends CardDef {
@field products = linksToMany(ProductCard);
static displayName = 'Product List';

static isolated = class Isolated extends Component<typeof this> {
// @ts-ignore TS1206: Decorators are not valid here.
@tracked filterText = '';

// @ts-ignore TS1206: Decorators are not valid here.
@action
updateFilter(event: Event) {
this.filterText = (event.target as any).value.toLowerCase();
}

get filteredProducts() {
let { filterText } = this;
if (!filterText) return this.args.model.products;
return this.args.model.products?.filter((product) => {
return product.title?.toLowerCase().includes(filterText);
});
}

get featuredProduct() {
return this.filteredProducts?.[0];
}

get productsForGrid() {
return this.filteredProducts?.slice(1) || [];
}

// @ts-ignore TS1206: Decorators are not valid here.
@action
viewProduct(model: ProductCard | undefined) {
if (model && this.args.context?.actions?.viewCard) {
this.args.context.actions.viewCard(model);
} else {
console.warn(
'Product card opening functionality is not available here.',
);
}
}

<template>
<div>
<div class='search-container'>
<BoxelInput
@type='search'
class='search-input'
placeholder='Search products...'
{{on 'input' this.updateFilter}}
/>
</div>
<div class='products-container'>
<div class='featured'>
<FeaturedProductComponent
@viewProduct={{this.viewProduct}}
@model={{this.featuredProduct}}
/>
</div>
<div class='grid'>
{{#each this.productsForGrid as |product|}}
<div class='grid-item'>
<EmbeddedProductComponent
@model={{product}}
{{on 'click' (fn this.viewProduct product)}}
class='grid-item-product'
/>
</div>
{{/each}}
</div>
</div>
</div>
<style>
.search-container {
background-image: url(https://i.imgur.com/PQuDAEo.jpg);
padding: var(--boxel-sp);
}
.search-input {
background-color: white;
color: black;
}
.search-input::placeholder {
color: var(--boxel-dark) !important;
}
.products-container {
padding: var(--boxel-sp);
}
.featured {
padding-bottom: var(--boxel-sp);
border-bottom: 2px solid black;
margin-bottom: var(--boxel-sp);
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 0;
}
.grid-item {
border-bottom: 1px solid var(--boxel-200);
padding-bottom: var(--boxel-sp-xxs);
margin-bottom: var(--boxel-sp-xs);
padding-right: var(--boxel-sp);
}
.grid-item:nth-child(4) {
padding-right: 0;
}
.grid-item-product {
cursor: pointer;
}
</style>
</template>
};

/*
static embedded = class Embedded extends Component<typeof this> {
<template></template>
}
static atom = class Atom extends Component<typeof this> {
<template></template>
}
static edit = class Edit extends Component<typeof this> {
<template></template>
}
*/
}
Loading

0 comments on commit e280f96

Please sign in to comment.