{"id":20178653,"url":"https://github.com/chanda-abdul/figma-merch-store","last_synced_at":"2025-10-03T22:59:32.050Z","repository":{"id":188608673,"uuid":"634092798","full_name":"Chanda-Abdul/figma-merch-store","owner":"Chanda-Abdul","description":"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.","archived":false,"fork":false,"pushed_at":"2023-09-15T00:14:39.000Z","size":141352,"stargazers_count":1,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-13T16:49:10.916Z","etag":null,"topics":["angular","angular-directives","angular-pipes","angular-services","bem","e-commerce","figma","frontend-practice","gsap","rxjs","scss"],"latest_commit_sha":null,"homepage":"https://soft-mermaid-23f7cd.netlify.app/","language":"SCSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Chanda-Abdul.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-29T03:04:28.000Z","updated_at":"2024-08-07T16:31:49.000Z","dependencies_parsed_at":null,"dependency_job_id":"3c7c0962-a656-4990-aa0e-6c30d8b670a1","html_url":"https://github.com/Chanda-Abdul/figma-merch-store","commit_stats":null,"previous_names":["chanda-abdul/figma-merch-store"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Chanda-Abdul%2Ffigma-merch-store","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Chanda-Abdul%2Ffigma-merch-store/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Chanda-Abdul%2Ffigma-merch-store/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Chanda-Abdul%2Ffigma-merch-store/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Chanda-Abdul","download_url":"https://codeload.github.com/Chanda-Abdul/figma-merch-store/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241616681,"owners_count":19991541,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","angular-directives","angular-pipes","angular-services","bem","e-commerce","figma","frontend-practice","gsap","rxjs","scss"],"created_at":"2024-11-14T02:22:15.813Z","updated_at":"2025-10-03T22:59:27.013Z","avatar_url":"https://github.com/Chanda-Abdul.png","language":"SCSS","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Figma Merch Store \n\n\nThis is a solution to the \u003ca target=\"_blank\" rel=\"noopener\" href=\"https://www.frontendpractice.com/projects/figma\"\u003eFigma Merch Store\"\u003c/a\u003e[]() challenge on \u003ca target=\"_blank\" rel=\"noopener\" href=\"https://www.frontendpractice.com/\"\u003eFrontend Practice\u003c/a\u003e. \n\n\n- View live demo of my solution \u003ca target=\"_blank\" rel=\"noopener\" href=\"https://soft-mermaid-23f7cd.netlify.app/\"\u003ehere\u003c/a\u003e\n\n- View \u003ca target=\"_blank\" rel=\"noopener\" href=\"https://store.figma.com/\"\u003estore.figma.com\u003c/a\u003e\n\n\n![Design preview for the Figma Merch Store  coding challenge](https://www.frontendpractice.com/_next/image?url=%2Ffullsize%2FC2-figma.png\u0026w=1200\u0026q=90)\n\n## Table of contents\n\n- [Overview](#overview)\n  - [The challenge](#the-challenge)\n  - [Demo](#demo)\n- [Features](#features)\n- [My process](#my-process)\n  - [Built with](#built-with)\n  - [Continued development](#continued-development)\n  - [What I learned](#what-i-learned)\n  - [Continued development](#continued-development)\n  - [Useful resources](#useful-resources)\n- [Author](#author)\n\n## Overview\n\n### The challenge\n\nCode 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).\n\n### Users should be able to:\n- [x] Toggle the dropdown search bar by clicking the search icon, allowing them to conveniently search for products.\n- [x] Filter the list of products by category.\n- [ ] See hover states for all interactive elements on the page, \n  \u003c!-- - [x] Product photo swap images on hover, \n  - [ ] Recreate the circle with rotating text and hover animation. --\u003e\n- [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\n- [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\n- [ ] Navigate through a smooth and streamlined checkout process, ensuring efficient completion of their purchase.\n- [x] Select a country and have the currency automatically update, ensuring accurate pricing information aligned with their chosen location.\n- [x] View and interact with all animated elements on the page\n  \u003c!-- - [ ] page elements apear on Scroll, etc --\u003e\n- [x] View the optimal layout for each page depending on their device's screen size\n  - [x] Mobile: `\u003c 900px`\n  - [x] Desktop: `\u003e 900px`\n\n## Demo\nView live demo [here](https://soft-mermaid-23f7cd.netlify.app/)\n\u003c!--Solution URL:\u003c/b\u003e [here](https://github.com/Chanda-Abdul/Angular-Multi-step-form) | \u003cb\u003eLive Site URL:\u003c/b\u003e [here](https://dazzling-crisp-559db7.netlify.app/) --\u003e\n\u003c!-- ### Screenshots\n\n![](https://www.frontendpractice.com/_next/image?url=%2Ffullsize%2FC2-figma.png\u0026w=1200\u0026q=90) --\u003e\n## Features\n### Animations\n  - [Draggable Image Slider](#draggable-slider-using-gsap)\n  - [Swap image on hover](#swap-image-on-hover)\n  - [Marquee](#marquee-animation)\n  - [Circle SVG with rotating text and hover animation](#circle-svg-with-rotating-text-and-hover-animation)\n### Styling\n  - [Custom Fonts](#custom-fonts)\n  - [Dropdown Search](#dropdown-search-bar)\n  - [Random color generation](#random-color-generation)\n### Angular/JavaScript\n  - [Custom Currency Pipe](#custom-currency-pipe)\n  - [Content filtering](#content-filtering)\n  - [Shopping Cart](#shopping-cart)\n  - [User Reviews/Ratings (Bonus)](#user-reviewsratings-component)\n  \u003c!-- - [Size Chart (Bonus)](#) --\u003e\n\n## Draggable Slider using GSAP\n\u003cimg src=\"https://img.shields.io/badge/JavaScript-323330?style=for-the-badge\u0026logo=javascript\u0026logoColor=F7DF1E\" alt=\"JavaScript icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge\u0026logo=css3\u0026logoColor=white\" alt=\"CSS icon\" height=\"28\" /\u003e![Green Sock](https://img.shields.io/badge/green%20sock-88CE02?style=for-the-badge\u0026logo=greensock\u0026logoColor=white)\n\n- Created a custom \u003ci\u003eAngular \u003c/i\u003eStructural **`@Directive`** to craft an interactive image slider with draggable functionality. The animation was created using **\u003ca target=\"_blank\" rel=\"noopener\" href=\"https://greensock.com\"\u003eGreenSock\u003c/a\u003e**'s `Draggable` feature.\n\n\n- In the **`/hero`** component \n\n    \u003cimg src=\"/src/assets/screens/herodrag.gif\" width=\"420\" alt=\"dragabble\"/\u003e\n\n\n- In the **`/product`** component (mobile view)\n\n    \u003cimg src=\"/src/assets/screens/productdrag.gif\" width=\"420\" alt=\"dragabble\"/\u003e\n\n- **`draggable-slider.directive.ts`**\n  ```ts\n    @Directive({\n      selector: '[dragSliderDir]'\n    })\n\n    export class DraggableSliderDirective {\n      draggable!: Draggable;\n\n      constructor(private imagesRef: ElementRef) { }\n\n      ngAfterViewInit() {\n         gsap.registerPlugin(Draggable);\n         this.initializeDragabbleSlider();\n      }\n\n      initializeDragabbleSlider() {\n        let content = this.imagesRef.nativeElement;\n        let slider = content.parentNode;\n\n        this.draggable = new Draggable(content, {\n          type: 'x',\n          repeat: -1,\n          edgeResistance: 2,\n          dragResistance: .1,\n          bounds: slider,\n          paused: true,\n          center: false,\n          throwProps: true,\n          snap: { x: [0, 100] }\n        })\n      }\n    }\n\n  ```\n- **`hero.component.html`**\n  ```html\n    \u003cdiv class=\"hero\"\u003e\n      \u003cdiv class=\"container\"\u003e\n        \u003cdiv class=\"draggable-images\"\n              dragSliderDir \u003e\n          \u003csvg\u003e\u003c/svg\u003e\n          \u003csvg\u003e\u003c/svg\u003e\n          \u003csvg\u003e\u003c/svg\u003e\n          \n          ...\n          \n        \u003c/div\u003e\n      \u003c/div\u003e\n      \u003c/div\u003e\n\n  ```\n- **`product.component.html`**\n\n  ```html\n    ...\n\n    \u003cdiv class=\"product-grid__thumbnails\"\u003e\n        \u003cdiv class=\"product-grid__thumbnails--mobile-drag\"\n                  dragSliderDir\u003e\n          \u003cfigure *ngFor=\"let photo of product.productPhotos; \n          let i = index\"\u003e\n              \u003cimg [src]=\"photo\"\n                  (click)=\"expandImg(i)\"\n                      alt=\"product.name\"\u003e\n          \u003c/figure\u003e\n\n        ...\n\n      \u003c/div\u003e\n    \u003c/div\u003e\n    \n  ```\n\n\u003ci\u003e**If anyone knows how to make this draggable slider an infinite loop please let me know**\u003c/i\u003e\n\n## Swap image on hover\n\n \u003cimg src=\"https://img.shields.io/badge/Angular-DD0031?style=for-the-badge\u0026logo=angular\u0026logoColor=white\" alt=\"Angular icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/Sass-CC6699?style=for-the-badge\u0026logo=sass\u0026logoColor=white\" alt=\"Sass icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge\u0026logo=css3\u0026logoColor=white\" alt=\"CSS icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge\u0026logo=html5\u0026logoColor=white\" alt=\"HTML icon\" height=\"28\" /\u003e\n\n- 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 \u003ci\u003eCSS animations\u003c/i\u003e, opacity and positioning.\n\n    \u003cimg src=\"/src/assets/screens/imagehoverswap.gif\" width=\"420\" alt=\"hover swap\"/\u003e\n\n- **`hover-img-swap.directive.ts`**\n  ```ts\n      @Directive({\n        selector: '[hoverImgSwap]',\n      })\n\n      export class HoverImgSwapDirective {\n\n        @HostBinding('class.hoverImgSwap')\n        get cssClasses() {\n          return true;\n        }\n      }\n  ```\n- **`product-card.component.html`**\n  ```html\n    \u003cdiv class=\"product-list__product\" hoverImgSwap\u003e\n\n        \u003cfigure\u003e\n\n          \u003cimg [src]=\"product.hoverPatternImg\"/\u003e\n\n          \u003cimg [src]=\"product.hoverProductImg\"/\u003e\n\n          \u003cimg [src]=\"product.productPhotos[0]\"/\u003e\n\n        \u003c/figure\u003e\n\n        ...\n\n    \u003c/div\u003e\n  ```\n- in **`_animations.scss`**\n\n  ```scss\n    .hoverImgSwap {\n\n      figure {\n        position: relative;\n        border-radius: $border-radius-default;\n        border: none;\n\n        img {\n          transition: border-color 750ms, opacity 750ms;\n          border-radius: $border-radius-default;\n          width: 100%;\n          height: 100%;\n          top: 0;\n          left: 0;\n          right: 0;\n          bottom: 0;\n          object-fit: cover;\n        }\n\n        :first-child,\n        :nth-child(2) {\n          position: absolute;\n          border: $border-default;\n          opacity: 0;\n        }\n\n        :nth-child(3) {\n          position: absolute;\n          opacity: 1;\n        }\n      }\n\n      \u0026:hover {\n        figure {\n          :first-child {\n            opacity: 1;\n            width: 100%;\n            height: 100%;\n          }\n        }\n\n        :nth-child(2) {\n          opacity: 1;\n          max-width: 100%;\n          max-height: 100%;\n          z-index: 2;\n        }\n\n        :nth-child(3) {\n          opacity: 0;\n        }\n      }\n    }\n  ```\n ## Marquee Animation\n\n \u003cimg src=\"https://img.shields.io/badge/JavaScript-323330?style=for-the-badge\u0026logo=javascript\u0026logoColor=F7DF1E\" alt=\"JavaScript icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge\u0026logo=css3\u0026logoColor=white\" alt=\"CSS icon\" height=\"28\" /\u003e![Green Sock](https://img.shields.io/badge/green%20sock-88CE02?style=for-the-badge\u0026logo=greensock\u0026logoColor=white)\n\n- In the **`/footer`**  component, I created a custom re-useable, \u003ci\u003eAngular \u003c/i\u003eStructural **`@Directive`**, to craft a scrolling **`\u003cmarquee\u003e`**  animation featuring both text and **`\u003csvg\u003e`** elements. This animation was achieved using **\u003ca target=\"_blank\" rel=\"noopener\" href=\"https://greensock.com/\"\u003eGreenSock\u003c/a\u003e** for seamless and dynamic motion.\n\n\u003cimg src=\"/src/assets/screens/marquee-bio.gif\" width=\"100%\" alt=\"marquee\"/\u003e\n\u003cimg src=\"/src/assets/screens/marquee-lilac.gif\" width=\"100%\" alt=\"marquee\"/\u003e\n\u003cimg src=\"/src/assets/screens/marquee-sunflower.gif\" width=\"100%\" alt=\"marquee\"/\u003e\n\u003cimg src=\"/src/assets/screens/marquee-glow.gif\" width=\"100%\" alt=\"marquee\"/\u003e\n\n- **`marquee.directive.ts`**\n  ```ts\n    @Directive({\n    selector: '[marqueeDirective]'\n    })\n\n    export class MarqueeDirective implements OnInit {\n\n      constructor(private elRef: ElementRef, private renderer: Renderer2) { }\n\n      ngOnInit(): void {\n        this.initializeMarquee();\n      }\n\n      initializeMarquee(): void {\n        let content = this.elRef.nativeElement.childNodes;\n\n        gsap.from(content, {\n          x: -this.elRef.nativeElement.offsetWidth,\n          repeat: -1,\n          duration: 15,\n          ease: 'linear'\n        })\n\n        gsap.to(content, {\n          x: this.elRef.nativeElement.offsetWidth,\n        })\n          .totalProgress(-.7)\n      }\n    }\n  ```\n\n\u003ci\u003e**If anyone knows how to make this marquee an infinite loop please let me know**\u003c/i\u003e \n\n\u003c!-- ##  Circle `\u003csvg\u003e` with rotating text and hover animation --\u003e\n\u003c!-- TO-DO =\u003e  (animations)  /HOME COMPONENT --\u003e\n\n\u003c!-- - [ ] Recreate the circle with rotating text and hover animation. --\u003e\n## Custom fonts\n\n\u003cimg src=\"https://img.shields.io/badge/Sass-CC6699?style=for-the-badge\u0026logo=sass\u0026logoColor=white\" alt=\"Sass icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge\u0026logo=css3\u0026logoColor=white\" alt=\"CSS icon\" height=\"28\" /\u003e\n\n- Custom fonts \u003ci\u003e\u003cb\u003e\u003ca target=\"_blank\" rel=\"noopener\" href=\"https://abcdinamo.com/typefaces/whyte\"\u003e\"Whyte\"\u003c/a\u003e\u003c/b\u003e\u003c/i\u003e and  \u003ci\u003e\u003cb\u003e\u003ca target=\"_blank\" rel=\"noopener\" href=\"https://abcdinamo.com/typefaces/whyte\"\u003e\"Whyte Inktrap\"\u003c/a\u003e\u003c/b\u003e\u003c/i\u003e. \n- \u003cb\u003eWhyte\u003c/b\u003e has smooth and sharp transitions, while \u003cb\u003eWhyte Inktrap\u003c/b\u003e has curt yet also curvy ink traps at its joints.  \n\u003ctable style=\"'border:none:'\"\u003e\n \u003ctr\u003e\n    \u003ctd\u003e\u003cb style=\"font-size:32px\"\u003eWhyte \u003c/b\u003e\u003cbr\u003e body font\u003c/td\u003e\n    \u003ctd\u003e\u003cb style=\"font-size:32px\"\u003eWhyte Inktrap \u003c/b\u003e\u003cbr\u003edisplay font\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n    \u003ctd\u003e \n    \u003cimg src=\"src/assets/screens/body-font-small-11.png\" width=\"375\"  /\u003e\n\n \u003c/td\u003e\n    \u003ctd\u003e\u003cimg src=\"src/assets/screens/display-font-small-1.png\" width=\"375\"  /\u003e\n\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n    \u003ctd\u003e \n   \n \u003cimg src=\"src/assets/screens/body-font-xl-4.png\" width=\"375\"  /\u003e\n \u003c/td\u003e\n    \u003ctd\u003e\n\u003cimg src=\"src/assets/screens/display-font-small-2.png\" width=\"375\"  /\u003e\u003c/td\u003e\n \u003c/tr\u003e\n\u003c/table\u003e\n\n## Dropdown search bar\n\n\u003cimg src=\"https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge\u0026logo=css3\u0026logoColor=white\" alt=\"CSS icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge\u0026logo=html5\u0026logoColor=white\" alt=\"HTML icon\" height=\"28\" /\u003e\n\ndropdown search bar when the \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"#000000\" viewBox=\"0 0 256 256\"\u003e\u003cpath d=\"M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z\"\u003e\u003c/path\u003e\u003c/svg\u003e icon  is clicked.\n\u003cbr\u003e\n    \u003cimg src=\"/src/assets/screens/searchdropdown.gif\" width=\"420\" alt=\"marquee\"/\u003e\n\n\u003c!-- TO-DO =\u003e (animations) (functionality) Add Chaotic sticker Sprinkle --\u003e\n\n\u003c!-- TO-DO =\u003e (styles) #SHOP COMPONENT --\u003e\n## Shopping Cart\n\n\u003cimg src=\"https://img.shields.io/badge/Angular-DD0031?style=for-the-badge\u0026logo=angular\u0026logoColor=white\" alt=\"Angular icon\" height=\"28\" /\u003e![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge\u0026logo=reactivex\u0026logoColor=white)\u003cimg src=\"https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge\u0026logo=css3\u0026logoColor=white\" alt=\"CSS icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge\u0026logo=html5\u0026logoColor=white\" alt=\"HTML icon\" height=\"28\" /\u003e\n\n- Implemented a user-friendly shopping cart. \n- The shopping cart feature allows users to add products to their cart and view their cart on the homepage for a streamlined shopping experience.\n\n\u003cimg src='/src/assets/screens/cart.png'  width=\"425\" alt='cart-screen'/\u003e\n\n\u003c!-- TO-DO =\u003e (functionality) (bonus) Implement checkout: Implement a checkout feature that allows users to enter their payment and shipping information and complete their purchase. --\u003e\n\n## Custom currency `@Pipe`\n\n\u003cimg src=\"https://img.shields.io/badge/Angular-DD0031?style=for-the-badge\u0026logo=angular\u0026logoColor=white\" alt=\"Angular icon\" height=\"28\" /\u003e![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge\u0026logo=reactivex\u0026logoColor=white)\u003cimg src=\"https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge\u0026logo=html5\u0026logoColor=white\" alt=\"HTML icon\" height=\"28\" /\u003e\n- Developed a custom Angular **`@Pipe`** for currency conversion, to update product prices based on the selected country. \n- Utilized the \u003ca target=\"_blank\" rel=\"noopener\" href=\"https://currencybeacon.com/api-documentation\"\u003eCurrencyBeacon API\u003c/a\u003e for most current exchange rates. \n- The default currency is \u003ci\u003e**USD**\u003c/i\u003e\n- Country can be selected by using the dropdown on the navigation menu. \n- The **`@Pipe`** converts currency amounts into \u003ci\u003eGBP (British Pound)\u003c/i\u003e, \u003ci\u003eJPY (Japanese Yen\u003c/i\u003e), \u003ci\u003eEUR (Euro)\u003c/i\u003e, or \u003ci\u003eCAD (Canadian Dollar)\u003c/i\u003e, providing users with accurate and up-to-date pricing information in their preferred currency.\n\n\n  \u003cimg src='/src/assets/screens/currency.gif' width=\"425\" alt='currency'/\u003e\n\n- **[`currency-conversion.pipe.ts`](/src/app/pipes/currency-conversion.pipe.ts)**\n\n    ```ts\n\n      ...\n\n      export class CurrencyConversionPipe implements PipeTransform {\n      \n      ...\n      \n      transform(amount: number, country: string, rates: any): any {\n          switch (country) {\n         \n          ...\n         \n          // United Kingdom\n          case 'store-uk':\n              return formatCurrency(amount * rates['GBP'].exchangeRate, 'en-us', '£', 'GBP', '1.0-0');\n          // Japan\n          case 'store-jp':\n              return formatCurrency(amount * rates['JPY'].exchangeRate, 'en-JP', '¥', 'JPY', '1.0-0');\n          // USA or Just browsing\n          default:\n              return formatCurrency(amount, 'en-US', '$', 'USD', '1.0-0');\n          }\n        }\n      }\n    ```\n\n- **[`products.service.ts`](/src/app/services/products.service.ts)**\n\n    ```ts\n      ...\n        loadExchangeRates(): Observable\u003cany\u003e {\n\n            return this.http.get\u003cany\u003e(`${environment.API_BASE_URL}/rates`, this.httpOptions)\n              .pipe(\n                map(res =\u003e\n                  res\n                ),\n                shareReplay(),\n                catchError((err) =\u003e {\n                  throw err + 'Request failed:';\n                })\n              )\n          }\n      ...\n\n    ```\n\n- **`@Pipe`** in the component template\n    ```html\n        ...\n            \u003cp class=\"product-list__product--price\"\u003e\n                {{ product.price |  \n                    currencyConversion : \n                    selectedCountry : \n                    (exchangeRates$ | async) \n                  }}\n            \u003c/p\u003e\n        ...\n\n    ```\n\n\n## Content filtering\n\u003cimg src=\"https://img.shields.io/badge/Angular-DD0031?style=for-the-badge\u0026logo=angular\u0026logoColor=white\" alt=\"Angular icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge\u0026logo=html5\u0026logoColor=white\" alt=\"HTML icon\" height=\"28\" /\u003e\n\n- Within  the `/call-to-action` users are able select **\"LAYERS\"** or **\"COMPONENTS\"** to filter `/product-list` by category.\n- Products are filtered through a custom Angular **`@Pipe`** . \n\u003c!-- insert .gif --\u003e\n- [**`filter-by-category.pipe.ts`**](/src/app/pipes/filter-by-category.pipe.ts)\n\n  ```ts\n      ...\n        PipeTransform {\n\n          transform(products: Product[], category?: string) {\n            if (category) {\n              return products.filter(product =\u003e product.tags[0] === category);\n\n            } else {\n              return products\n            }\n          }\n        }\n  ```\n\n\u003c!-- insert filter img --\u003e\n\n## Random color generation\n\u003cimg src=\"https://img.shields.io/badge/Sass-CC6699?style=for-the-badge\u0026logo=sass\u0026logoColor=white\" alt=\"Sass icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge\u0026logo=css3\u0026logoColor=white\" alt=\"CSS icon\" height=\"28\" /\u003e\n\n### Brand Colors\n \u003cimg src=\"/src/assets/screens/brandColors.png\" alt=\"brand-colors\"/\u003e\n \n- Upon rendering, one of the brand's colors is randomly chosen as the `background-color` for the `/footer` component. \n- 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.\n\n \u003cimg src=\"/src/assets/screens/footer.gif\" width=\"100%\"  /\u003e\n\n - A `$random` color variable was created to use as an accent color in the `/reviews` and `/size-chart` components. \n - `$random` is updated on render.\n \n  - **`_variables.scss`**\n    ```css\n\n      $bgColors: (\n          $bio-punk,\n          $placid-lilac,\n          $fiery-glow,\n          $sunflower\n      );\n\n      $key: random(length($bgColors));\n\n      $nth: nth($bgColors, $key );\n\n      $random: $nth !default;\n    ```\n\n  - **`review.component.scss`**\n    ```scss\n      .review {\n          background-color: rgba($random, .05);\n          border: 2px solid rgba($random, .25);\n          }\n    ```\n\n \u003c!-- size-chart --\u003e\n\n##  User Reviews/Ratings Component\n\u003cimg src=\"https://img.shields.io/badge/Angular-DD0031?style=for-the-badge\u0026logo=angular\u0026logoColor=white\" alt=\"Angular icon\" height=\"28\" /\u003e![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge\u0026logo=reactivex\u0026logoColor=white)\u003cimg src=\"https://img.shields.io/badge/Sass-CC6699?style=for-the-badge\u0026logo=sass\u0026logoColor=white\" alt=\"Sass icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge\u0026logo=html5\u0026logoColor=white\" alt=\"HTML icon\" height=\"28\" /\u003e\n\n- 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 \u003ci\u003elayers\u003c/i\u003e or \u003ci\u003ecomponents\u003c/i\u003e. \n\n  ```ts\n    export class ReviewComponent implements OnInit {\n      ...\n      reviews$!: Observable\u003cReview[]\u003e;\n      averageRating: number = 0;\n      averageRatingStars: string = Array(5).fill(`\u003cspan\u003e\u0026#9734;\u003c/span\u003e`).join(``);\n      ...\n\n      ngOnInit(): void {\n        this.loadReviews();\n      }\n\n      loadReviews() {\n        const reviews$ = this.reviewsService.\n        getRandomReviews(this.reviewCategory).pipe(\n            tap(ratings =\u003e {\n              this.updateAverages(ratings);\n            })\n          );\n\n        this.reviews$ = reviews$;\n      }\n\n      updateAverages(ratings:Review[]) {\n        let average = 0;\n\n        ratings.forEach((rating: Review) =\u003e { average += rating.rating });\n\n        average /= ratings.length;\n\n        this.averageRating = average;\n\n        average = Math.round(average);\n\n        this.averageRatingStars = this.getStars(average);\n\n        const averages = {\n          numberOfReviews: ratings.length,\n          averageRating: this.averageRating,\n          averageRatingStars: this.averageRatingStars\n        }\n\n        this.newAverages.emit(averages);\n      }\n\n\n      getStars(starRating: number): string {\n        return Array(starRating).fill(`\u003cspan\u003e\u0026#9733;\u003c/span\u003e`)\n          .concat(Array(5 - starRating).fill(`\u003cspan\u003e\u0026#9734;\u003c/span\u003e`))\n          .join(``);\n      }\n    }\n  ```\n\n- Uses the `/reviews/:tag` endpoint\n\n  ```ts\n      app.get('/reviews/:tag', (req, res) =\u003e {\n    \n      const tag = req.params.tag;\n\n      const reviewOptions = [\n        ...REVIEWS.filter((review) =\u003e review.type == tag),\n        ...REVIEWS.filter((review) =\u003e review.type == 'generic'),\n      ];\n    \n      let randomRatings = reviewOptions\n        .sort(() =\u003e 0.5 - Math.random())\n        .slice(0, Math.floor(Math.random() * reviewOptions.length));\n\n      return res.status(200).json(randomRatings.slice(0, 8));\n    });\n  ```\n-  `averageRating` and `averageRatingStars` are computed from the `reviews$`. Afterward, the interface displays `averageRatingStars`,  alongside a numerical `averageRating` and the total count of \"user reviews\".\n\n \u003cimg src=\"/src/assets/screens/reviews.png\" width=\"414\"  /\u003e\n\n\u003c!-- ## Size Chart Dynamic Component (Bonus) --\u003e\n\u003c!-- insert .gif --\u003e\n# My Process\nI enjoyed working on this project it was a nice balance of styling requirements and functional requirements great project to practice with.\n## Built with\n\u003cimg src=\"https://img.shields.io/badge/Angular-DD0031?style=for-the-badge\u0026logo=angular\u0026logoColor=white\" alt=\"Angular icon\" height=\"28\" /\u003e![RxJS](https://img.shields.io/badge/rxjs-%23B7178C.svg?style=for-the-badge\u0026logo=reactivex\u0026logoColor=white)\u003cimg src=\"https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge\u0026logo=typescript\u0026logoColor=white\" alt=\"TypeScript icon\" height=\"28\" /\u003e![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge\u0026logo=javascript\u0026logoColor=black)\u003cimg src=\"https://img.shields.io/badge/Sass-CC6699?style=for-the-badge\u0026logo=sass\u0026logoColor=white\" alt=\"Sass icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge\u0026logo=css3\u0026logoColor=white\" alt=\"CSS icon\" height=\"28\" /\u003e![Green Sock](https://img.shields.io/badge/green%20sock-88CE02?style=for-the-badge\u0026logo=greensock\u0026logoColor=white)\u003cimg src=\"https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge\u0026logo=html5\u0026logoColor=white\" alt=\"HTML icon\" height=\"28\" /\u003e![BEM](https://img.shields.io/static/v1?style=for-the-badge\u0026message=BEM\u0026color=000000\u0026logo=BEM\u0026logoColor=FFFFFF\u0026label=)![Node](https://img.shields.io/badge/Node.js-43853D?style=for-the-badge\u0026logo=node.js\u0026logoColor=white)![Express](https://img.shields.io/badge/Express.js-404D59?style=for-the-badge)\u003cimg src=\"https://img.shields.io/badge/Axios-5A29E4?logo=axios\u0026logoColor=fff\u0026style=flat-square\" alt=\"Axios Badge\" height=\"28\" \u003e![Nodemon](https://img.shields.io/badge/NODEMON-%23323330.svg?style=for-the-badge\u0026logo=nodemon\u0026logoColor=%BBDEAD)\u003cimg src=\"https://img.shields.io/badge/Netlify-00C7B7?style=for-the-badge\u0026logo=netlify\u0026logoColor=white\" alt=\"Netlify icon\" height=\"28\" /\u003e\u003cimg src=\"https://img.shields.io/badge/Figma-F24E1E?style=for-the-badge\u0026logo=figma\u0026logoColor=white\" alt=\"Figma icon\" height=\"28\" /\u003e![Vercel](https://img.shields.io/badge/Vercel-000000?style=for-the-badge\u0026logo=vercel\u0026logoColor=white)\n\n## Continued development\n- dynamic `\u003csvg\u003e`'s in hero\n- infinite loop dragabble sliders and marquee\n\u003c!-- featured products suggest in product --\u003e\n## What I learned\n\n### GSAP\n\n### CSS Grid\n### Angular routing\nSet up routing: Set up routing so that users can navigate between pages. used `/product/:id` `/product/:name` to route to project page\n### Custom `@Pipe`'s\n\n- Developed a custom Angular [`@Pipe` for currency conversion](#custom-currency-pipe), to update prices based on the selected country. \n- Developed a custom Angular [`@Pipe`](#content-filtering)to filter `/product-list` by category(tag) .\n### Angular `@Directive`\n- Implemented custom structural directives to enable reusable and scalable animations in the application. \n- 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. \n- By encapsulating animation logic within directives, I was able to achieve modularity while reducing code duplication. \n### Angular in-memory-web-api \n\n### Display products with data binding\nUsed Angular's data binding and router params to display the `/product-list` of `/product-card`'s which route to each `/product` detail pages.  \n### Stateless Observable Service using RxJs and Angular Services\n- 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. \n- 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.\n### JSON Proxy server to store and retrieve data\nDuring \u003ci\u003edevelopment\u003c/i\u003e I used \u003cb\u003eJSON Proxy server\u003c/b\u003e to store and retrieve data, which was replaced with an express/node server and a database for \u003ci\u003eproduction\u003c/i\u003e.\n### API Integration\nFor \u003ci\u003eproduction\u003c/i\u003e I built an API using \u003cb\u003eNode\u003c/b\u003e and \u003cb\u003eExpress\u003c/b\u003e, hosted through \u003cb\u003e[Vercel](https://vercel.com/)\u003c/b\u003e, and integrated through \u003cb\u003e[RapidAPI](https://rapidapi.com/)\u003c/b\u003e.\n  #### API Endpoints\n  ##### `/products`\n  - returns a list of `PRODUCTS`\n  ##### `/products/search/:searchTerm` \n  - returns list of `PRODUCTS` filtered by `searchTerm`\n  ##### `/products/featured`\n  - returns list of featured `PRODUCTS`\n  ##### `/product/:productId`\n  - returns a `product` from the `PRODUCT` list by `:productId`\n  ##### `/reviews/:tag`\n  - returns up to 8 random `reviews` and ratings based on `product:tag`\n  ##### `/rates`\n  - returns most recent `exchangeRates` from the\n  \u003ca target=\"_blank\" rel=\"noopener\" href=\"https://currencybeacon.com/api-documentation\"\u003eCurrencyBeacon API\u003c/a\u003e\n\n## Useful resources\n\n\u003c!-- - []() --\u003e\n- [Angular Data Sharing Reference](https://github.com/H3AR7B3A7/EarlyAngularProjects/tree/master/data-sharing)\n- [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)\n- [Create a Shopping Cart Using Angular and Local Storage with PayPal Checkout](https://youtu.be/cWRG2gaZYQw)\n- [Scrolling Ticker Tape Web Design Tutorial](https://youtu.be/UKHXjhyumF0)\n- [The right way to componentize SVGs for your Angular app](https://cloudengineering.studio/articles/the-right-way-to-componentize-svgs-for-your-angular-app)\n- [Angular Currency Pipe \u0026 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.\n- [Angular CurrencyPipe](https://angular.io/api/common/CurrencyPipe)\n- [Proxy Server](#) - JSON server to store and retrieve data during development\n- [Angular in-memory-web-api](#)\n- [phosphor icons](https://phosphoricons.com/)\n- [:nth-child() pseudo-class](https://www.w3.org/TR/selectors/#nth-child-pseudo)\n- [CSS Grid Generator](https://cssgrid-generator.netlify.app/)\n- [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\n- [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.\n\n### Design Resources \u0026 Inspiration\n- [noize.com - View Product](https://noize.com/products/womens-organic-activewear-square-neck-top)\n- [quince.com - View Product](https://www.quince.com/women/silk-v-neck-cami?color=ivory\u0026gender=women\u0026tracker=collection_page__women%2Fbest-sellers__All%20Products__5)\n- [Dribble - Reviews-and-ratings](https://dribbble.com/shots/21512658-Reviews-and-ratings) \n\n## Author\n\n- Portfolio - [Chanda Abdul](https://www.Chandabdul.dev)\n- GitHub - [github.com/Chanda-Abdul](https://github.com/Chanda-Abdul)\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchanda-abdul%2Ffigma-merch-store","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchanda-abdul%2Ffigma-merch-store","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchanda-abdul%2Ffigma-merch-store/lists"}