Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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.
- Host: GitHub
- URL: https://github.com/chanda-abdul/figma-merch-store
- Owner: Chanda-Abdul
- Created: 2023-04-29T03:04:28.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2023-09-15T00:14:39.000Z (about 1 year ago)
- Last Synced: 2023-09-15T16:17:31.798Z (about 1 year ago)
- Topics: angular, angular-directives, angular-pipes, angular-services, bem, e-commerce, figma, frontend-practice, gsap, rxjs, scss
- Language: SCSS
- Homepage: https://soft-mermaid-23f7cd.netlify.app/
- Size: 135 MB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
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
![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
- In the **`/product`** component (mobile view)
- **`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
...
...
```**If anyone knows how to make this draggable slider an infinite loop please let me know**
## Swap image on hover
- 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-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![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.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
- 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
dropdown search bar when the icon is clicked.
## Shopping Cart
![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge&logo=reactivex&logoColor=white)
- 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.## Custom currency `@Pipe`
![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge&logo=reactivex&logoColor=white)
- 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-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
- 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
### 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
![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge&logo=reactivex&logoColor=white)- 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
![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge&logo=reactivex&logoColor=white)![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)![Green Sock](https://img.shields.io/badge/green%20sock-88CE02?style=for-the-badge&logo=greensock&logoColor=white)![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)![Nodemon](https://img.shields.io/badge/NODEMON-%23323330.svg?style=for-the-badge&logo=nodemon&logoColor=%BBDEAD)![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)