{"id":28856430,"url":"https://github.com/superdev093/prog-onboarding","last_synced_at":"2025-07-22T23:02:33.264Z","repository":{"id":109986998,"uuid":"337494890","full_name":"superdev093/prog-onboarding","owner":"superdev093","description":"A more detailed example of progressive onboarding using an NFT mint + sale shop example.","archived":false,"fork":false,"pushed_at":"2021-02-09T15:27:30.000Z","size":316,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-20T00:15:06.715Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":false,"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/superdev093.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":"2021-02-09T18:13:57.000Z","updated_at":"2023-01-06T05:47:01.000Z","dependencies_parsed_at":"2023-03-13T13:59:43.422Z","dependency_job_id":null,"html_url":"https://github.com/superdev093/prog-onboarding","commit_stats":null,"previous_names":["carlominds/prog-onboarding","getarobertson/prog-onboarding","superdev093/prog-onboarding"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/superdev093/prog-onboarding","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superdev093%2Fprog-onboarding","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superdev093%2Fprog-onboarding/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superdev093%2Fprog-onboarding/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superdev093%2Fprog-onboarding/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/superdev093","download_url":"https://codeload.github.com/superdev093/prog-onboarding/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superdev093%2Fprog-onboarding/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266586839,"owners_count":23952199,"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-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"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":[],"created_at":"2025-06-20T00:13:30.771Z","updated_at":"2025-07-22T23:02:33.245Z","avatar_url":"https://github.com/superdev093.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Live App Review 6 - Progressive Onboarding\n\nThis repo is a companion to the \"NFT Example Progressive Onboarding\" video series:\n\n[![Live App Review 6 - NFT Example - Part 1 Contract](https://img.youtube.com/vi/Y-HYCcYVmz8/0.jpg)](https://youtu.be/Y-HYCcYVmz8)\n[![Live App Review 6 - NFT Example - Part 2 App Demo and Tests](https://img.youtube.com/vi/W29QmxiJh84/0.jpg)](https://youtu.be/W29QmxiJh84)\n[![Live App Review 6 - NFT Example - Part 3 Frontend](https://img.youtube.com/vi/8kbxBqDSe_A/0.jpg)](https://youtu.be/8kbxBqDSe_A)\n\n# Notes on this Example\n\nThis example is not an NFT Standard and should be considered \"experimental\". However, it does work and is deployable to testnet. Prior to deploying to mainnet, please join our [Discord](https://near.chat) and check with NEAR **#dev-support** if there are any security concerns. \n\n## Quickstart\n```js\nyarn // see installation notes for Rust\nyarn test:unit // 1 test will fail due to bug in mocked blockchain\nyarn test:deploy // runs /test/app.test.js (all passing)\nyarn start\n```\n\nWIP Server Tests incomplete e.g. `yarn test:server` will not work.\n\nDespite server tests being incomplete, you still need to run the server (see notes below) `yarn \u0026\u0026 yarn start`.\n\nAfter running `yarn start` you'll have an app running (localhost:1234).\n\nThe app has 2 types of logins for the user.\n1. Connect Wallet\n2. Sign In As Guest\n\nConnect Wallet is a standard NEAR App flow (think allow this app to view my account from MetaMask).\n\nSign In As Guest creates a temporary keypair on the dev account where the contract is deployed. This gives the guest 3 free mints.\n\nWhen a guest mints, the owner_id of the TokenData is going to be the implicit account of the guest. See video \"Part 1 Contract\" for a detailed explanation of this.\n\nThe guest can earn NEAR tokens from sales (other users with NEAR purchase their artwork) and then decide how to use their sale proceeds. Typically they will fund an account.\n\n## NEAR Wallet Testnet has no \"Funding Account Flow\"\n\nIn the video \"Part 2 App and Testing\" I am running the NEAR Wallet on localhost.\n\nOn testnet NEAR Wallet funds accounts on behalf of users and developers to make the experience easy.\n\nIf you'd like to test the funding account flow yourself, clone the [NEAR Wallet](https://github.com/near/near-wallet) and run it with a `.env` file in the root with the following entry `DISABLE_CREATE_ACCOUNT=true`.\n\n## Installation\n\nBeyond having npm and node (latest versions), you should have Rust installed. I recommend nightly because living on the edge is fun.\n\nhttps://rustup.rs/\n\nAlso recommend installing near-cli globally\n\n`npm i -g near-cli`\n\nEverything else can be installed via:\n`yarn`\n`cd server \u0026\u0026 yarn`\n\n## NEAR Config\n\nThere is only one config.js file found in `src/config.js`, this is also used for running tests.\n\nUsing `src/config.js` you can set up your different environments. Use `REACT_APP_ENV` to switch environments e.g. in `package.json` script `deploy`.\n\n## Running Tests\n\nYou can run unit tests in the Rust contracts themselves, but it may be more useful to JS tests against testnet itself.\n\nNote: to run the app and server tests make sure you install and start the server.\n- cd server\n- yarn \u0026\u0026 yarn start\n\nCommands:\n- `test` will simply run app tests against the contract already deployed. You can mess around with `app.test.js` and try different frontend stuff\n- `test:deploy` - will deploy a new dev account (`/neardev`) and deploy a new contract to this account, then run `test`\n- `test:server` - will test the server, make sure you start it (see \"Note\" above)\n- `test:unit` - runs the rust unit tests\n\nIf you've changed your contract or your dev account has run out of funds use `test:deploy`, if you're updating your JS tests only then use `test`.\n\n## Test Utils\n\nThere are helpers in `test/test-utils.js` that take care of:\n1. creating a near connection and establishing a keystore for the dev account\n2. creating test accounts each time a test is run\n3. establishing a contract instance so you can call methods\n\nYou can change the default funding amount for test accounts in `src/config.js`\n\n## Using the NEAR Config in your app\n\nIn `src/state/near.js` you will see that `src/config.js` is loaded as a function. This is to satisfy the jest/node test runner.\n\nYou can destructure any properies of the config easily in any module you import it in like this:\n\n```\n// example file app.js\n\nimport getConfig from '../config';\nexport const {\n\tGAS,\n\tnetworkId, nodeUrl, walletUrl, nameSuffix,\n\tcontractName,\n} = getConfig();\n```\nNote the export const in the destructuring?\n\nNow you can import these like so:\n```\n//example file Component.js\nimport { GAS } from '../app.js'\n...\nawait contract.withdraw({ amount: parseNearAmount('1') }, GAS)\n...\n```\n\n# Notes on React 17, Parcel with useContext and useReducer\n- Bundled with Parcel 2.0 (@next) \u0026\u0026 eslint\n- *Minimal all-in-one state management with async/await support*\n\n## Getting Started: State Store \u0026 useContext\n\n\u003eThe following steps describe how to use `src/utils/state` to create and use your own `store` and `StateProvider`.\n\n1. Create a file e.g. `/state/app.js` and add the following code\n```js\nimport { State } from '../utils/state';\n\n// example\nconst initialState = {\n\tapp: {\n\t\tmounted: false\n\t}\n};\n\nexport const { store, Provider } = State(initialState);\n```\n2. Now in your `index.js` wrap your `App` component with the `StateProvider`\n```js\nimport { Provider } from './state/app';\n\nReactDOM.render(\n    \u003cProvider\u003e\n        \u003cApp /\u003e\n    \u003c/Provider\u003e,\n    document.getElementById('root')\n);\n```\n3. Finally in `App.js` you can `useContext(store)`\n```js\nconst { state, dispatch, update } = useContext(store);\n```\n\n## Usage in Components\n### Print out state values\n```js\n\u003cp\u003eHello {state.foo \u0026\u0026 state.foo.bar.hello}\u003c/p\u003e\n```\n### Update state directly in component functions\n```js\nconst handleClick = () =\u003e {\n    update('clicked', !state.clicked);\n};\n```\n### Dispatch a state update function (action listener)\n```js\nconst onMount = () =\u003e {\n    dispatch(onAppMount('world'));\n};\nuseEffect(onMount, []);\n```\n## Dispatched Functions with context (update, getState, dispatch)\n\nWhen a function is called using dispatch, it expects arguments passed in to the outer function and the inner function returned to be async with the following json args: `{ update, getState, dispatch }`\n\nExample of a call:\n```js\ndispatch(onAppMount('world'));\n```\n\nAll dispatched methods **and** update calls are async and can be awaited. It also doesn't matter what file/module the functions are in, since the json args provide all the context needed for updates to state.\n\nFor example:\n```js\nimport { helloWorld } from './hello';\n\nexport const onAppMount = (message) =\u003e async ({ update, getState, dispatch }) =\u003e {\n\tupdate('app', { mounted: true });\n\tupdate('clicked', false);\n\tupdate('data', { mounted: true });\n\tawait update('', { data: { mounted: false } });\n\n\tconsole.log('getState', getState());\n\n\tupdate('foo.bar', { hello: true });\n\tupdate('foo.bar', { hello: false, goodbye: true });\n\tupdate('foo', { bar: { hello: true, goodbye: false } });\n\tupdate('foo.bar.goodbye', true);\n\n\tawait new Promise((resolve) =\u003e setTimeout(() =\u003e {\n\t\tconsole.log('getState', getState());\n\t\tresolve();\n\t}, 2000));\n\n\tdispatch(helloWorld(message));\n};\n```\n## Prefixing store and Provider\n\nThe default names the `State` factory method returns are `store` and `Provider`. However, if you want multiple stores and provider contexts you can pass an additional `prefix` argument to disambiguate.\n\n```js\nexport const { appStore, AppProvider } = State(initialState, 'app');\n```\n\n## Performance and memo\n\nThe updating of a single store, even several levels down, is quite quick. If you're worried about components re-rendering, use `memo`:\n```js\nimport React, { memo } from 'react';\n\nconst HelloMessage = memo(({ message }) =\u003e {\n\tconsole.log('rendered message');\n\treturn \u003cp\u003eHello { message }\u003c/p\u003e;\n});\n\nexport default HelloMessage;\n```\nHigher up the component hierarchy you might have:\n```js\nconst App = () =\u003e {\n\tconst { state, dispatch, update } = useContext(appStore);\n    ...\n\tconst handleClick = () =\u003e {\n\t\tupdate('clicked', !state.clicked);\n\t};\n\n\treturn (\n\t\t\u003cdiv className=\"root\"\u003e\n\t\t\t\u003cHelloMessage message={state.foo \u0026\u0026 state.foo.bar.hello} /\u003e\n\t\t\t\u003cp\u003eclicked: {JSON.stringify(state.clicked)}\u003c/p\u003e\n\t\t\t\u003cbutton onClick={handleClick}\u003eClick Me\u003c/button\u003e\n\t\t\u003c/div\u003e\n\t);\n};\n```\nWhen the button is clicked, the component HelloMessage will not re-render, it's value has been memoized (cached). Using this method you can easily prevent performance intensive state updates in further down components until they are neccessary.\n\nReference:\n- https://reactjs.org/docs/context.html\n- https://dmitripavlutin.com/use-react-memo-wisely/\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperdev093%2Fprog-onboarding","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuperdev093%2Fprog-onboarding","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperdev093%2Fprog-onboarding/lists"}