{"id":19318768,"url":"https://github.com/mayvenn/storefront","last_synced_at":"2025-08-09T16:18:16.901Z","repository":{"id":31564157,"uuid":"35128888","full_name":"Mayvenn/storefront","owner":"Mayvenn","description":"The front of the store","archived":false,"fork":false,"pushed_at":"2023-10-01T20:27:10.000Z","size":63055,"stargazers_count":46,"open_issues_count":0,"forks_count":6,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-22T17:41:29.496Z","etag":null,"topics":["basscss","clojure","clojurescript","react","storefront"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","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/Mayvenn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null}},"created_at":"2015-05-05T23:26:52.000Z","updated_at":"2024-05-31T07:54:22.000Z","dependencies_parsed_at":"2023-10-01T21:19:27.305Z","dependency_job_id":null,"html_url":"https://github.com/Mayvenn/storefront","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/Mayvenn/storefront","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mayvenn%2Fstorefront","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mayvenn%2Fstorefront/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mayvenn%2Fstorefront/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mayvenn%2Fstorefront/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Mayvenn","download_url":"https://codeload.github.com/Mayvenn/storefront/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mayvenn%2Fstorefront/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267604430,"owners_count":24114534,"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","status":"online","status_checked_at":"2025-07-28T02:00:09.689Z","response_time":68,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["basscss","clojure","clojurescript","react","storefront"],"created_at":"2024-11-10T01:19:37.547Z","updated_at":"2025-07-28T23:35:07.408Z","avatar_url":"https://github.com/Mayvenn.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mayvenn Storefront\n[The front of shop.mayvenn.com](http://shop.mayvenn.com).\n\n# NOTE: This document represents our goals, not necessarily our current state.\n\n## Overview\nStorefront is built primarily using ClojureScript and Om, with a smidgen of Clojure.\n\n## Directory Structure\nStorefront's source is first split into three src directories: `src-clj`, `src-cljs`, `src-cljc`.\nNaturally, these dirs correspond to the language files contained within them.\nThe differences between the three files will be talked about more in a later section,\nfor now let's refer to the top level as `src*`.\n\nThe next layer of directories corelates to modules.\nA module generally corresponds to a single feature or logical area of storefront.\nMore on those in a bit.\n\nInside each module, there are\ncomponents (representing a single react component of the site, both appearance and behavior),\nhooks (direct integration with javascript),\naccessors (transformation functions for commonly used datastructure),\na single routes file (which defines that module's routes)\nand a core file (which simply imports all of the components for that module to make importing all cljs components easier).\n\n\n## Modules\nThere are 9 modules: core, account, catalog, checkout, dashboard, gallery, home, login.\n\n### Core\nThis is the frame of storefront.\nIt is the 'glue' which ties all of the modules together and it contains some utilities which are necessary across all modules.\nIf code needs to be in more than one module, it probably goes into this module\n\n### Account\nThis module contains the code involved in editing a user or stylist's information\n\n### Catalog\nThis module contains the code involved in shopping.\n\n### Checkout\nThis module contains the code involved in checking out.\n\n### Dashboard\nThis module contains the code involved in the stylist dashboard and the cash out now code.\n\n### Gallery\nThis module contains the code involved in stylist gallery\n\n### Home\nThis module contains the code involved in home page and static content\n\n### Login\nThis module contains the code involved in logging in.\n\n## Architecture\nStorefront's is built around a single application state and 5 event systems.\nAn event is a vector of keywords.\n\n### State\nStorefront's application state (let's call it app-state) is a single om atom containing nested maps.\nIt is dereferenced before it is in a usuable context, ie event handling code described below.\nWe set and access values through `keypaths` which are vectors of keywords.\nFor example, say you want to get the ID number of an order.\nThat information is stored in the order, naturally.\nYou can use the keypaths/order-number along with get-in to fetch that value.\n\n#### Example\n```\n(def app-state\n {:order {:number \"W123456\"}\n  :ui    {:other :stuff}})\n\n(get-in app-state keypaths/order-number)\n```\n\n### Events\nEvents are evaluated in a cascading manner, for example, examine the event `navigate-checkout-returning-or-guest`.\nIt evaluates to `[:navigate :checkout :returning :or :guest]`.\n\n#### Multimethods\nThere are many multimethods for which events can have implementations.\nThey are listed here in order of execution.\n\n##### Transitions\n```clojure\n\n(defmulti transition-state\n  (fn [dispatch event arguments app-state]\n    dispatch))\n\n```\n\nIf a given event has a transition defmethod registered, then that code will execute and update application state.\nIt should not be side-effectful and it must always return the application's state\n\n##### Effects\nIf a given event has a effect defmethod registered, then that code will execute perform side-effectful operations\nsuch as API calls, external library operations or interactions with cookies.\nIt should not have any side effects and it must always return the application's state.\nEffects can also dispatch further events.\n\n##### Query\nAll components should implement a query defmethod.\nQuery is responsible for transforming data in app-state into the shape that the component needs.\n\n##### Display\nAll components should implement a display defmethod.\nDisplay is responsible for building the component.\n\n##### Trackings\n\n#### Executing Events\nAs the function handle-message processes the event for most event multimethods,\nit executes left to right, building as it goes.\nThe exceptions are the Query and Display steps (we can only render one component for a given place at a time!).\n`handle-message` finishes all event steps for a given defmethod before continuing.\n\nSo, it would execute the following events:\n1. Transition\n   1. `[:navigate]`\n   1. `[:navigate :checkout]`\n   1. `[:navigate :checkout :returning]`\n   1. `[:navigate :checkout :returning :or]`\n   1. `[:navigate :checkout :returning :or :guest]`\n1. Effect\n   1. `[:navigate]`\n   1. `[:navigate :checkout]`\n   1. `[:navigate :checkout :returning]`\n   1. `[:navigate :checkout :returning :or]`\n   1. `[:navigate :checkout :returning :or :guest]`\n1. Query\n   1. `[:navigate :checkout :returning :or :guest]`\n1. Display\n   1. `[:navigate :checkout :returning :or :guest]`\n1. Trackings\n   1. `[:navigate]`\n   1. `[:navigate :checkout]`\n   1. `[:navigate :checkout :returning]`\n   1. `[:navigate :checkout :returning :or]`\n   1. `[:navigate :checkout :returning :or :guest]`\n\n\nNote that not all intermediate events need to have defmultis implemented.\nSome examples would be `[:navigate :checkout :returning]` and `[:navigate :checkout :returning :or]`.\n\n#### Examples:\n##### Fetching data asynchronously over an API\n``` clojure\n(defn get-promotions\n  [cache]\n  (cache-req\n   cache\n   GET\n   \"/promotions\"\n   request-keys/get-promotions\n   {:handler #(messages/handle-message events/api-success-promotions %)}))\n\n(defmethod transition-state events/api-success-promotion\n  [dispatch event {:keys [promotions]} app-state]\n  (assoc-in app-state keypaths/promotions promotions))\n```\n\nAbove we have defined the function `get-promotions` to fetch the current\npromotions from a backend service.\n\nAttached is a handler that will run once a response is received. The handler places\n`api-success-promotions` into the event queue along with the response from the\napi request.\n\nThe transition-state defmethod registered for the `api-success-promotion` event\nreceives the response and places the returned promotions into the appropriate place in app-state.\n\n\n##### Configuring a component to update application state when interacted with\n```clojure\n(defn text-field [app-state keypath]\n  [:input\n   {:type      \"text\"\n    :value     (get-in app-state keypath)\n    :on-change (fn [^js/Event e]\n                 (handle-message events/control-change-state\n                                 {:keypath keypath\n                                  :value   (.. e -target -value)}))}])\n\n(defmethod transition-state events/control-change-state\n  [dispatch event {:keys [keypath value] :as arguments} app-state]\n  (assoc-in app-state keypath (if (fn? value) (value) value)))\n```\nIn the above example, we can see a simple function which takes the state of the\napplication and a keypath that points to a value in app state and returns a html data structure.\nBefore it can be used, it must used in an om component.\n\nThe value of the text field is not directly manipulated by the text field.\nInstead, the on-change fn dispatches a `control-change-state` event.\n\nThe transition multimethod for the `control-change-state` event places the value\ninto app-state causing the text-field to re-render with the newly inserted value in app-state.\n\n## Server Side Rendering\n\n## Questions\n* Where are the tests?\n  * The vast majority of Storefront's tests are in a different project.\n  This was done because they are integration tests and test more than just storefront.\n  There are some tests for the server side handler.\n* Can I run this?\n  * Sure, but you will need to setup up some external dependencies, such as an API server. We hope this project serves as a reference project moreso than generic ecommerce solution.\n\n## License\nCopyright (C) Mayvenn, Inc. - All Rights Reserved\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmayvenn%2Fstorefront","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmayvenn%2Fstorefront","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmayvenn%2Fstorefront/lists"}