Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/chanda-abdul/figma-merch-store

This is my solution to the Figma Merch Store(https://store.figma.com/) challenge on https://www.frontendpractice.com. Built with Angular, RxJs, and GSAP.
https://github.com/chanda-abdul/figma-merch-store

angular angular-directives angular-pipes angular-services bem e-commerce figma frontend-practice gsap rxjs scss

Last synced: 2 days ago
JSON representation

This is my solution to the Figma Merch Store(https://store.figma.com/) challenge on https://www.frontendpractice.com. Built with Angular, RxJs, and GSAP.

Awesome Lists containing this project

README

        

# Figma Merch Store

This is a solution to the Figma Merch Store"[]() challenge on Frontend Practice.

- View live demo of my solution here

- View store.figma.com

![Design preview for the Figma Merch Store coding challenge](https://www.frontendpractice.com/_next/image?url=%2Ffullsize%2FC2-figma.png&w=1200&q=90)

## Table of contents

- [Overview](#overview)
- [The challenge](#the-challenge)
- [Demo](#demo)
- [Features](#features)
- [My process](#my-process)
- [Built with](#built-with)
- [Continued development](#continued-development)
- [What I learned](#what-i-learned)
- [Continued development](#continued-development)
- [Useful resources](#useful-resources)
- [Author](#author)

## Overview

### The challenge

Code a replication of the [Figma Merch Store](https://store.figma.com/) site, from this [frontend-practice](https://www.frontendpractice.com/) [project](https://www.frontendpractice.com/projects/figma).

### Users should be able to:
- [x] Toggle the dropdown search bar by clicking the search icon, allowing them to conveniently search for products.
- [x] Filter the list of products by category.
- [ ] See hover states for all interactive elements on the page,

- [x] Utilize a draggable slider to effortlessly explore featured products within the hero section and also to view product thumbnails while using the mobile viewport
- [x] Add, remove, and update products to their shopping cart, ensuring a convenient shopping experience and enabling them to review their selections before finalizing a purchase
- [ ] Navigate through a smooth and streamlined checkout process, ensuring efficient completion of their purchase.
- [x] Select a country and have the currency automatically update, ensuring accurate pricing information aligned with their chosen location.
- [x] View and interact with all animated elements on the page

- [x] View the optimal layout for each page depending on their device's screen size
- [x] Mobile: `< 900px`
- [x] Desktop: `> 900px`

## Demo
View live demo [here](https://soft-mermaid-23f7cd.netlify.app/)

## Features
### Animations
- [Draggable Image Slider](#draggable-slider-using-gsap)
- [Swap image on hover](#swap-image-on-hover)
- [Marquee](#marquee-animation)
- [Circle SVG with rotating text and hover animation](#circle-svg-with-rotating-text-and-hover-animation)
### Styling
- [Custom Fonts](#custom-fonts)
- [Dropdown Search](#dropdown-search-bar)
- [Random color generation](#random-color-generation)
### Angular/JavaScript
- [Custom Currency Pipe](#custom-currency-pipe)
- [Content filtering](#content-filtering)
- [Shopping Cart](#shopping-cart)
- [User Reviews/Ratings (Bonus)](#user-reviewsratings-component)

## Draggable Slider using GSAP
JavaScript iconCSS icon![Green Sock](https://img.shields.io/badge/green%20sock-88CE02?style=for-the-badge&logo=greensock&logoColor=white)

- Created a custom Angular Structural **`@Directive`** to craft an interactive image slider with draggable functionality. The animation was created using **GreenSock**'s `Draggable` feature.

- In the **`/hero`** component

dragabble

- In the **`/product`** component (mobile view)

dragabble

- **`draggable-slider.directive.ts`**
```ts
@Directive({
selector: '[dragSliderDir]'
})

export class DraggableSliderDirective {
draggable!: Draggable;

constructor(private imagesRef: ElementRef) { }

ngAfterViewInit() {
gsap.registerPlugin(Draggable);
this.initializeDragabbleSlider();
}

initializeDragabbleSlider() {
let content = this.imagesRef.nativeElement;
let slider = content.parentNode;

this.draggable = new Draggable(content, {
type: 'x',
repeat: -1,
edgeResistance: 2,
dragResistance: .1,
bounds: slider,
paused: true,
center: false,
throwProps: true,
snap: { x: [0, 100] }
})
}
}

```
- **`hero.component.html`**
```html








...



```
- **`product.component.html`**

```html
...




product.name

...




```

**If anyone knows how to make this draggable slider an infinite loop please let me know**

## Swap image on hover

Angular iconSass iconCSS iconHTML icon

- In the **`/product-list`** component, a custom **`@Directive`** was created to swap **`/product-card`**'s default cover image to a pattern/image on **`:hover`**, using CSS animations, opacity and positioning.

hover swap

- **`hover-img-swap.directive.ts`**
```ts
@Directive({
selector: '[hoverImgSwap]',
})

export class HoverImgSwapDirective {

@HostBinding('class.hoverImgSwap')
get cssClasses() {
return true;
}
}
```
- **`product-card.component.html`**
```html

...


```
- in **`_animations.scss`**

```scss
.hoverImgSwap {

figure {
position: relative;
border-radius: $border-radius-default;
border: none;

img {
transition: border-color 750ms, opacity 750ms;
border-radius: $border-radius-default;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
object-fit: cover;
}

:first-child,
:nth-child(2) {
position: absolute;
border: $border-default;
opacity: 0;
}

:nth-child(3) {
position: absolute;
opacity: 1;
}
}

&:hover {
figure {
:first-child {
opacity: 1;
width: 100%;
height: 100%;
}
}

:nth-child(2) {
opacity: 1;
max-width: 100%;
max-height: 100%;
z-index: 2;
}

:nth-child(3) {
opacity: 0;
}
}
}
```
## Marquee Animation

JavaScript iconCSS icon![Green Sock](https://img.shields.io/badge/green%20sock-88CE02?style=for-the-badge&logo=greensock&logoColor=white)

- In the **`/footer`** component, I created a custom re-useable, Angular Structural **`@Directive`**, to craft a scrolling **``** animation featuring both text and **``** elements. This animation was achieved using **GreenSock** for seamless and dynamic motion.

marquee
marquee
marquee
marquee

- **`marquee.directive.ts`**
```ts
@Directive({
selector: '[marqueeDirective]'
})

export class MarqueeDirective implements OnInit {

constructor(private elRef: ElementRef, private renderer: Renderer2) { }

ngOnInit(): void {
this.initializeMarquee();
}

initializeMarquee(): void {
let content = this.elRef.nativeElement.childNodes;

gsap.from(content, {
x: -this.elRef.nativeElement.offsetWidth,
repeat: -1,
duration: 15,
ease: 'linear'
})

gsap.to(content, {
x: this.elRef.nativeElement.offsetWidth,
})
.totalProgress(-.7)
}
}
```

**If anyone knows how to make this marquee an infinite loop please let me know**

## Custom fonts

Sass iconCSS icon

- Custom fonts "Whyte" and "Whyte Inktrap".
- Whyte has smooth and sharp transitions, while Whyte Inktrap has curt yet also curvy ink traps at its joints.


Whyte
body font
Whyte Inktrap
display font












## Dropdown search bar

CSS iconHTML icon

dropdown search bar when the icon is clicked.


marquee

## Shopping Cart

Angular icon![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge&logo=reactivex&logoColor=white)CSS iconHTML icon

- Implemented a user-friendly shopping cart.
- The shopping cart feature allows users to add products to their cart and view their cart on the homepage for a streamlined shopping experience.

cart-screen

## Custom currency `@Pipe`

Angular icon![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge&logo=reactivex&logoColor=white)HTML icon
- Developed a custom Angular **`@Pipe`** for currency conversion, to update product prices based on the selected country.
- Utilized the CurrencyBeacon API for most current exchange rates.
- The default currency is **USD**
- Country can be selected by using the dropdown on the navigation menu.
- The **`@Pipe`** converts currency amounts into GBP (British Pound), JPY (Japanese Yen), EUR (Euro), or CAD (Canadian Dollar), providing users with accurate and up-to-date pricing information in their preferred currency.

currency

- **[`currency-conversion.pipe.ts`](/src/app/pipes/currency-conversion.pipe.ts)**

```ts

...

export class CurrencyConversionPipe implements PipeTransform {

...

transform(amount: number, country: string, rates: any): any {
switch (country) {

...

// United Kingdom
case 'store-uk':
return formatCurrency(amount * rates['GBP'].exchangeRate, 'en-us', '£', 'GBP', '1.0-0');
// Japan
case 'store-jp':
return formatCurrency(amount * rates['JPY'].exchangeRate, 'en-JP', '¥', 'JPY', '1.0-0');
// USA or Just browsing
default:
return formatCurrency(amount, 'en-US', '$', 'USD', '1.0-0');
}
}
}
```

- **[`products.service.ts`](/src/app/services/products.service.ts)**

```ts
...
loadExchangeRates(): Observable {

return this.http.get(`${environment.API_BASE_URL}/rates`, this.httpOptions)
.pipe(
map(res =>
res
),
shareReplay(),
catchError((err) => {
throw err + 'Request failed:';
})
)
}
...

```

- **`@Pipe`** in the component template
```html
...


{{ product.price |
currencyConversion :
selectedCountry :
(exchangeRates$ | async)
}}


...

```

## Content filtering
Angular iconHTML icon

- Within the `/call-to-action` users are able select **"LAYERS"** or **"COMPONENTS"** to filter `/product-list` by category.
- Products are filtered through a custom Angular **`@Pipe`** .

- [**`filter-by-category.pipe.ts`**](/src/app/pipes/filter-by-category.pipe.ts)

```ts
...
PipeTransform {

transform(products: Product[], category?: string) {
if (category) {
return products.filter(product => product.tags[0] === category);

} else {
return products
}
}
}
```

## Random color generation
Sass iconCSS icon

### Brand Colors
brand-colors

- Upon rendering, one of the brand's colors is randomly chosen as the `background-color` for the `/footer` component.
- A logo is also selected at random, ensuring that it differs in color from the background. Each time a re-render occurs, a fresh combination is generated.

- A `$random` color variable was created to use as an accent color in the `/reviews` and `/size-chart` components.
- `$random` is updated on render.

- **`_variables.scss`**
```css

$bgColors: (
$bio-punk,
$placid-lilac,
$fiery-glow,
$sunflower
);

$key: random(length($bgColors));

$nth: nth($bgColors, $key );

$random: $nth !default;
```

- **`review.component.scss`**
```scss
.review {
background-color: rgba($random, .05);
border: 2px solid rgba($random, .25);
}
```

## User Reviews/Ratings Component
Angular icon![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge&logo=reactivex&logoColor=white)Sass iconHTML icon

- Upon the initial render of `/product` component, up to 8 random "user reviews" and star ratings are generated based on `product:tag`. Each product has a `product:tag` category of layers or components.

```ts
export class ReviewComponent implements OnInit {
...
reviews$!: Observable;
averageRating: number = 0;
averageRatingStars: string = Array(5).fill(``).join(``);
...

ngOnInit(): void {
this.loadReviews();
}

loadReviews() {
const reviews$ = this.reviewsService.
getRandomReviews(this.reviewCategory).pipe(
tap(ratings => {
this.updateAverages(ratings);
})
);

this.reviews$ = reviews$;
}

updateAverages(ratings:Review[]) {
let average = 0;

ratings.forEach((rating: Review) => { average += rating.rating });

average /= ratings.length;

this.averageRating = average;

average = Math.round(average);

this.averageRatingStars = this.getStars(average);

const averages = {
numberOfReviews: ratings.length,
averageRating: this.averageRating,
averageRatingStars: this.averageRatingStars
}

this.newAverages.emit(averages);
}

getStars(starRating: number): string {
return Array(starRating).fill(``)
.concat(Array(5 - starRating).fill(``))
.join(``);
}
}
```

- Uses the `/reviews/:tag` endpoint

```ts
app.get('/reviews/:tag', (req, res) => {

const tag = req.params.tag;

const reviewOptions = [
...REVIEWS.filter((review) => review.type == tag),
...REVIEWS.filter((review) => review.type == 'generic'),
];

let randomRatings = reviewOptions
.sort(() => 0.5 - Math.random())
.slice(0, Math.floor(Math.random() * reviewOptions.length));

return res.status(200).json(randomRatings.slice(0, 8));
});
```
- `averageRating` and `averageRatingStars` are computed from the `reviews$`. Afterward, the interface displays `averageRatingStars`, alongside a numerical `averageRating` and the total count of "user reviews".

# My Process
I enjoyed working on this project it was a nice balance of styling requirements and functional requirements great project to practice with.
## Built with
Angular icon![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge&logo=reactivex&logoColor=white)TypeScript icon![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)Sass iconCSS icon![Green Sock](https://img.shields.io/badge/green%20sock-88CE02?style=for-the-badge&logo=greensock&logoColor=white)HTML icon![BEM](https://img.shields.io/static/v1?style=for-the-badge&message=BEM&color=000000&logo=BEM&logoColor=FFFFFF&label=)![Node](https://img.shields.io/badge/Node.js-43853D?style=for-the-badge&logo=node.js&logoColor=white)![Express](https://img.shields.io/badge/Express.js-404D59?style=for-the-badge)Axios Badge![Nodemon](https://img.shields.io/badge/NODEMON-%23323330.svg?style=for-the-badge&logo=nodemon&logoColor=%BBDEAD)Netlify iconFigma icon![Vercel](https://img.shields.io/badge/Vercel-000000?style=for-the-badge&logo=vercel&logoColor=white)

## Continued development
- dynamic ``'s in hero
- infinite loop dragabble sliders and marquee

## What I learned

### GSAP

### CSS Grid
### Angular routing
Set up routing: Set up routing so that users can navigate between pages. used `/product/:id` `/product/:name` to route to project page
### Custom `@Pipe`'s

- Developed a custom Angular [`@Pipe` for currency conversion](#custom-currency-pipe), to update prices based on the selected country.
- Developed a custom Angular [`@Pipe`](#content-filtering)to filter `/product-list` by category(tag) .
### Angular `@Directive`
- Implemented custom structural directives to enable reusable and scalable animations in the application.
- These directives were utilized in the [footer marquee](#marquee-animation), [product hover image swap](#swap-image-on-hover), and [draggable image slider](#draggable-slider-using-gsap) components.
- By encapsulating animation logic within directives, I was able to achieve modularity while reducing code duplication.
### Angular in-memory-web-api

### Display products with data binding
Used Angular's data binding and router params to display the `/product-list` of `/product-card`'s which route to each `/product` detail pages.
### Stateless Observable Service using RxJs and Angular Services
- Developed stateless observable services following the principles of MVC/MVVM architecture, strategically minimizing client-side state storage and instead dynamically retrieving data from the server on demand.
- Implemented this approach seamlessly within components **[`product.service.ts`](/src/app/services/products.service.ts)**, **[`cart.service.ts`](/src/app/services/cart.service.ts)**, and **[`ratings.service.ts`](/src/app/services/ratings.service.ts)**, enhancing efficiency and maintaining a clean separation of concerns.
### JSON Proxy server to store and retrieve data
During development I used JSON Proxy server to store and retrieve data, which was replaced with an express/node server and a database for production.
### API Integration
For production I built an API using Node and Express, hosted through [Vercel](https://vercel.com/), and integrated through [RapidAPI](https://rapidapi.com/).
#### API Endpoints
##### `/products`
- returns a list of `PRODUCTS`
##### `/products/search/:searchTerm`
- returns list of `PRODUCTS` filtered by `searchTerm`
##### `/products/featured`
- returns list of featured `PRODUCTS`
##### `/product/:productId`
- returns a `product` from the `PRODUCT` list by `:productId`
##### `/reviews/:tag`
- returns up to 8 random `reviews` and ratings based on `product:tag`
##### `/rates`
- returns most recent `exchangeRates` from the
CurrencyBeacon API

## Useful resources

- [Angular Data Sharing Reference](https://github.com/H3AR7B3A7/EarlyAngularProjects/tree/master/data-sharing)
- [How to Secure Angular Environment Variables for Use in GitHub Actions](https://betterprogramming.pub/how-to-secure-angular-environment-variables-for-use-in-github-actions-39c07587d590)
- [Create a Shopping Cart Using Angular and Local Storage with PayPal Checkout](https://youtu.be/cWRG2gaZYQw)
- [Scrolling Ticker Tape Web Design Tutorial](https://youtu.be/UKHXjhyumF0)
- [The right way to componentize SVGs for your Angular app](https://cloudengineering.studio/articles/the-right-way-to-componentize-svgs-for-your-angular-app)
- [Angular Currency Pipe & Format Currency In Angular with examples](https://www.angularjswiki.com/angular/angular-currency-pipe-formatting-currency-in-angular/) - Angular Currency Pipe is one of the bulit in pipe in Angular used to format currency value according to given country code,currency,decimal,locale information.
- [Angular CurrencyPipe](https://angular.io/api/common/CurrencyPipe)
- [Proxy Server](#) - JSON server to store and retrieve data during development
- [Angular in-memory-web-api](#)
- [phosphor icons](https://phosphoricons.com/)
- [:nth-child() pseudo-class](https://www.w3.org/TR/selectors/#nth-child-pseudo)
- [CSS Grid Generator](https://cssgrid-generator.netlify.app/)
- [Udemy: Reactive Angular Course (with RxJs, Angular 16) by Angular University](https://www.udemy.com/course/rxjs-reactive-angular-course) - Build Angular 16 Applications in Reactive style with plain RxJs - Patterns, Anti-Patterns, Lightweight State Management
- [Build your own API](https://youtu.be/GK4Pl-GmPHk) - Youtube video that quickly shows you how to make a profitable API and sell it on the RapidAPI Hub.

### Design Resources & Inspiration
- [noize.com - View Product](https://noize.com/products/womens-organic-activewear-square-neck-top)
- [quince.com - View Product](https://www.quince.com/women/silk-v-neck-cami?color=ivory&gender=women&tracker=collection_page__women%2Fbest-sellers__All%20Products__5)
- [Dribble - Reviews-and-ratings](https://dribbble.com/shots/21512658-Reviews-and-ratings)

## Author

- Portfolio - [Chanda Abdul](https://www.Chandabdul.dev)
- GitHub - [github.com/Chanda-Abdul](https://github.com/Chanda-Abdul)