# Overview The _Layerr_ artist platform provides a front-end tool for publishing NFT projects to the web using Layerr's hosting and marketing services. ## Usage `npm run dev` -Development Environment ## Deployment `npm run build` -Builds the application for production `npm run start` -Runs the application in production mode ## Testing `npm run test` -Runs the test suite # Cheatsheet ## useLayerrStore `useLayerrStore` contains a set of Zustand stores that provide functions for interacting with the Layerr API, and a set of state variables for managing the state of these models on the front-end. There are 4 stores, one for each model type: `useProjectStore`, `useCollectionStore`, `useTokenStore`, and `useFileStore`. See `src/components/Project/Card/Card.js` for an example of how to use the `useProjectStore` hook ### Full Example ```javascript import React from 'react'; import { useProjectStore } from 'src/store/useLayerrStore'; export function ProjectEditor() { const project = useProjectStore.use.selectedModel(); const createProject = useProjectStore.use.create(); const updateProject = useProjectStore.use.save(); const deleteProject = useProjectStore.use.delete(); const selectNextProject = useProjectStore.use.selectNext(); const selectPreviousProject = useProjectStore.use.selectPrev(); const loading = useProjectStore.use.isLoading(); return ( <div> <div>{project.name}</div> <button onClick={() => selectPreviousProject()}>Previous Project</button> <button onClick={() => createProject({ name: 'New Project' })}> Create Project </button> <button onClick={() => updateProject({ name: 'Updated Project' })}> Update Project </button> <button onClick={() => deleteProject()}>Delete Project</button> <button onClick={() => selectNextProject()}>Next Project</button> </div> ); } ``` ### Use Each store provides a convenience object called `use` as suggested by the Zustand documentation, which makes initializing the state in components easier. Standard Zustand usage: ```javascript import React from 'react'; import { useProjectStore } from 'src/store/useLayerrStore'; export function ProjectName() { const project = useProjectStore((state) => state.selectedModel); return <div>{project.name}</div>; } ``` With `use`: ```javascript import React from 'react'; import { useProjectStore } from 'src/store/useLayerrStore'; export function ProjectName() { const project = useProjectStore.use.selectedModel(); return <div>{project.name}</div>; } ``` Do whatever you want, it's just a convenience object. The array pick and object pick methods for Zustand with `zustand/shallow` can have undesired side effects, so we recommend using the `use` object and selecting the state variables you need individually unless you want those side effects. ### State Each store provides a set of state variables for managing the state of the model on the front-end. There are a few lower-level state management functions, and the Zustand provided subscriber, but these are the most important ones: ```javascript /** * @typedef {Object} LayerrUseStore * @property {T[]} models - The models in the store * @property {Number} selectedIndex - The index of the selected model * @property {T} selectedModel - The selected model * @property {(model: T) => Promise<T>} create - Creates a model * @property {(idx: Number) => void} select - Selects a model by index * @property {() => void} selectNext - Selects the next model * @property {() => void} selectPrev - Selects the previous model * @property {(model?: T) => void} delete - Deletes a model, or the selected model * @property {Boolean} isLoading - Whether the store is loading */ ``` TODO: Perhaps adding a sort function would be useful, so that the model array can be reordered from the front-end without writing logic code in the components. # Architecture The Layerr artist platform is built using React, Zustand, and React Router. Zustand is used for state management, and React Router is used for routing. ## Back-End The back-end is built using Netlify Functions, which provides a serverless environment for running NodeJS functions on Netlify. The back-end is used to provide a RESTful API for interacting with the Layerr backend, and to provide a generic CRUD interface for storing user data and files. The client also directly accesses the EVM for contract interactions, and uses the `web3-token` library to sign messages for authentication with the Layerr backend. # Layerr API The Layerr API is a RESTful API for interacting with the Layerr backend. It is used to create and manage projects, upload files, and publish projects to the web. The Layerr API client is implemented on top of the `ky` library in `src/utilities/layerr.js`, the API automatically handled authentication with integrations to ## Storage The `/netlify/functions/store` endpoint provides a single generic CRUD interface over `Model`s stored in MongoDB and S3 for user data and file storage, authenticated based on wallet address. Each wallet can only access its own objects in storage. By specifying query params, e.g. `GET /netlify/functions/store?model=File`, a `Model` type can be selected. `Model` _must_ be defined for the query to be valid. _HTTP Methods_ dictate the type of operation performed, and respect idempotency rules of PUT (whereas POST is not idempotent, since it creates a new `Model` for every call). `GET` - Read. Users may provide query parameters to match sets of objects, or provide an ID to get a single object, or provide no query parameters at all to get all objects owned by the authenticated user. `POST` - Create. Users provide all required fields of the `Model` specified to make a new object. The `_id` field is provided by the backend implementation. `POST` - Update. Idempotent batch updater, which can also receive an `upsert=true` query parameter to create objects that aren't matched by the query. `DELETE` - Delete. Removes the object from storage. Valid `Model` types are `File`, `ArtCollection`, `Token` and `Project`. All further query parameters are passed to the store handler (Mongo for `Project`, `ArtCollection`, and `Token`, AWS for `File`). Models served by the Mongo handler expose Mongoose find parameters to any remaining queryparams. Example queries: - Get All Projects `GET /netlify/functions/store?model=Project` - Get one project by name `GET /netlify/functions/store?model=Project&name=My%20Project` - Create a new project `POST /netlify/functions/store?model=Project Body:<JSON>` - Update a project, or create it if it doesn't exist `PUT /netlify/functions/store?model=Project&upsert=true Body:<JSON>` ### Mongoose Mongoose governs the `Model` interface. In order to add a new `Model` type, a new Mongoose schema must be defined in `netlify/functions/store.js`, and a JSDoc type should be defined in `netlify/functions/store.js` to describe the schema (if you're using VSCode, this will allow you to get autocomplete for the schema). ### MongoDB Storage in MongoDB is a transparent store of application data for the user, which allow synchronizing appdata between applications on different devices, and the Layerr Backend. ### AWS S3 Not implemented, intended store for user files. ## Authentication Authentication is done using `web3-token` to sign messages that authorize Layerr to act as an agent of the user for only those specified purposes. Each Layerr API endpoint requires a unique and valid wallet signature to be passed in the `Authorization` header as a `Bearer` token. The Layerr API automatically requests a signature from the user when the user attempts to use a backend feature that requires authentication without an accompanying `layerr-token-<endpoint>` token in local storage, and stores the token for continued use on that device. These signatures expire as specificed in the `approvals` object in `src/utilities/layerr.js`, defaulting to 30 Days for all endpoints except `publish`, which expires after 5 minutes. # Layerr SDK & Zustand Integration On top of the Layerr API, the Layerr SDK provides a Zustand store for managing the state of the Layerr API, and provides a set of hooks for interacting with the application state required to manage the underlying Layerr API state. Available in `src/store/useLayerrStore.js` the `useProjectStore`, `useCollectionStore`, `useTokenStore`, and `useFileStore` hooks provides a set of functions for interacting with the Layerr API, and a set of state variables for managing the state of these models on the front-end. The SDK automatically persists data down to the database, and hydrates and persists to local storage to allow for offline use. Each store provides a convenience function called `use` as suggested by the Zustand documentation, which makes initializing the state in components easier. See `src/components/Project/Card/Card.js` for an example of how to use the `useProjectStore` hook to manage the state of a nested component without relying on props, or interacting with `id`s. ## State Management Best Practices Don't use React useState, Redux, or any other state management libraries. Manage state with Zustand and React Context. Don't use `id` fields to refer to objects. Just use the models themselves, using local references only. Zustand stores are designed to be used as a single source of truth for the application state, and should be used as such. # Library Updates All libraries have been updated to their latest stable versions, and should be periodically refreshed to ensure compatibility with the latest versions of React and rapidly changing web3 libraries. ## Router React Router was updated to version 6, which provides a new API for managing routes which were rewritten to use the new BrowserRouter component in `src/App.js`. General navigation layout is handled by `src/routes/Layout.js`, and a data loading / auth gate is provided in `src/routes/Entry.js`. # Things to be fixed A few React components weren't compatible with upgrades to React18 and ReactRouter6. Some of these components were refactored surgically to use the new API, but others were left as-is or commented out entirely. These components should be refactored to use the new API. The biggest change is to the SectionManager, which can no longer make use of the react-draggable-list library, which was deprecated in favor of react-beautiful-dnd. The new library is much more powerful, but requires a complete rewrite of the SectionManager component, which is currently commented out in `src/components/web-builder/builder-components/builder-sections/builder-draggable-list/BuilderSectionManager.js` `src/Screens/Dashboard.js` needs CSS fixes to tile the array of projects. `src/components/web-builder/builder-components/builder-sections/builder-draggable-list/BuilderSectionManager.js` is commented out, and needs to be refactored to use new storage libraries and development best practices. # Netlify & DNS Integrations Netlify integrated for backend hosting with the `netlify dev` command (aliased by `npm run dev`). DNS implementation pending. # Deploy Pipeline Barebones implementation, pending further development.