diff --git a/README.md b/README.md index ce14352..31ca2f1 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,17 @@

+ + --- This template is a starter template for building an Algorand based dApp on React with [`subtopia-js-sdk`](https://github.com/subtopia-algo/subtopia-js) integration. The template is based on the official [`algokit-react-template`](https://github.com/algorandfoundation/algokit-react-frontend-template). ## Features -The template offers two presets, the recommended preset includes all of the above, a custom preset allows you to select which features you want to include in your project. +The template offers two presets, the recommended preset includes all of the features included in the official [algokit-react-frontend-template](https://github.com/algorandfoundation/algokit-react-frontend-template), a custom preset allows you to select which features you want to include in your project. The full list of features includes the following: diff --git a/template_content/README.md.jinja b/template_content/README.md.jinja index 6cd5477..30a6553 100644 --- a/template_content/README.md.jinja +++ b/template_content/README.md.jinja @@ -1,6 +1,10 @@ # {{ project_name }} -This starter React project has been generated using AlgoKit. See below for default getting started instructions. +This starter Subtopia integrated dApp project has been generated using AlgoKit. See below for default getting started instructions. + +- For detailed documentation on Subtopia platform refer to [Subtopia documentation](https://docs.subtopia.io). +- To learn more about Subtopia JavaScript SDK refer to [SDK documentation](https://docs.subtopia.io/integrations/javascript-sdk). +- To see other more generic examples in Next.js, React, Vue.js and Svelte refer to [Subtopia examples](https://github.com/subtopia-algo/subtopia-js-examples) # Setup diff --git a/template_content/package.json.jinja b/template_content/package.json.jinja index 5563845..db2af04 100644 --- a/template_content/package.json.jinja +++ b/template_content/package.json.jinja @@ -10,37 +10,37 @@ "node": ">=18.0" }, "devDependencies": { - "@types/node": "18.17.14", - "@types/react": "18.2.11", - "@types/react-dom": "18.2.4", - "@vitejs/plugin-react-swc": "3.3.2", - "autoprefixer": "10.4.14", + "@types/node": "^18.17.14", + "@types/react": "^18.2.11", + "@types/react-dom": "^18.2.4", + "@vitejs/plugin-react-swc": "^3.3.2", + "autoprefixer": "^10.4.14", {% if use_eslint_prettier -%} - "eslint": "8.42.0", - "eslint-config-prettier": "8.8.0", - "eslint-plugin-prettier": "5.0.0", - "@typescript-eslint/eslint-plugin": "6.5.0", - "@typescript-eslint/parser": "6.5.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^5.0.0", + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", {% endif -%} {% if use_tailwind -%} "postcss": "^8.4.24", - "tailwindcss": "3.3.2", + "tailwindcss": "^3.3.2", {% endif -%} {% if use_jest -%} "ts-jest": "^29.1.1", - "@types/jest": "29.5.2", + "@types/jest": "^29.5.2", {% endif -%} - "ts-node": "10.9.1", - "typescript": "5.1.6", + "ts-node": "^10.9.1", + "typescript": "^5.1.6", {% if use_playwright -%} "@playwright/test": "^1.35.0", "playwright": "^1.35.0", {% endif -%} - "vite": "4.4.9" + "vite": "^5.0.5" }, "dependencies": { "@walletconnect/modal-sign-html": "^2.6.1", - "@algorandfoundation/algokit-utils": "^4.1.0", + "@algorandfoundation/algokit-utils": "^5.0.1", "@blockshake/defly-connect": "^1.1.6", "@daffiwallet/connect": "^1.0.3", "@perawallet/connect": "^1.3.1", @@ -51,7 +51,9 @@ {% endif -%} "notistack": "^3.0.1", "react": "^18.2.0", - "react-dom": "18.2.0", + "react-dom": "^18.2.0", + "react-use": "^17.4.2", + "subtopia-js-sdk": "^1.0.0", "tslib": "^2.6.2" }, "scripts": { diff --git a/template_content/src/App.tsx b/template_content/src/App.tsx index 79657e2..a0fab1b 100644 --- a/template_content/src/App.tsx +++ b/template_content/src/App.tsx @@ -6,7 +6,7 @@ import algosdk from 'algosdk' import { SnackbarProvider } from 'notistack' import { useState } from 'react' import ConnectWallet from './components/ConnectWallet' -import Transact from './components/Transact' +import Transact from './components/Demo' import { getAlgodConfigFromViteEnvironment, getKmdConfigFromViteEnvironment } from './utils/network/getAlgoClientConfigs' let providersArray: ProvidersArray @@ -64,34 +64,34 @@ export default function App() { return ( -
-
-
+
+
+

- Welcome to
AlgoKit 🙂
+ Welcome to
Subtopia.io
AlgoKit example

- This starter has been generated using official AlgoKit React template. Refer to the resource below for next steps. + This starter has been generated using Subtopia AlgoKit template. Refer to the resources below for next steps.

Getting started
- {activeAddress && ( - )}
diff --git a/template_content/src/components/Demo.tsx.jinja b/template_content/src/components/Demo.tsx.jinja new file mode 100644 index 0000000..0a9a214 --- /dev/null +++ b/template_content/src/components/Demo.tsx.jinja @@ -0,0 +1,143 @@ +import * as algokit from '@algorandfoundation/algokit-utils' +import { useWallet } from '@txnlab/use-wallet' +import { useSnackbar } from 'notistack' +import { useEffect, useState } from 'react' +import { useAsyncRetry } from 'react-use' +import { ChainType, Duration, ProductState, SUBTOPIA_REGISTRY_ID, SubtopiaClient } from 'subtopia-js-sdk' +import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs' + +interface DemoInterface { + openModal: boolean + setModalState: (value: boolean) => void +} + +const DUMMY_SMI_ID = 481312144 + +const Demo = ({ openModal, setModalState }: DemoInterface) => { + const [loading, setLoading] = useState(false) + + const algodConfig = getAlgodConfigFromViteEnvironment() + const algodClient = algokit.getAlgoClient({ + server: algodConfig.server, + port: algodConfig.port, + token: algodConfig.token, + }) + + const { enqueueSnackbar } = useSnackbar() + const { signer, activeAddress } = useWallet() + const [subtopiaClient, setSubtopiaClient] = useState() + + const subscriptionResponse = useAsyncRetry(async () => { + if (!activeAddress || !subtopiaClient) { + return undefined + } + + return await subtopiaClient.getSubscription({ + algodClient: algodClient, + subscriberAddress: activeAddress, + }) + }, [activeAddress]) + + const [subscriptionState, setSubscriptionState] = useState(undefined) + + const handleSubscribe = async () => { + setLoading(true) + + if (!signer || !activeAddress || !subtopiaClient) { + enqueueSnackbar('Please connect wallet first', { variant: 'warning' }) + return + } + + try { + enqueueSnackbar('Sending transaction...', { variant: 'info' }) + const response = await subtopiaClient + .createSubscription({ + subscriber: { + addr: activeAddress, + signer: signer, + }, + duration: Duration.MONTH, + }) + .catch(() => { + setLoading(false) + }) + + if (!response) { + throw new Error('Failed to send transaction') + } + + subscriptionResponse.retry() + enqueueSnackbar(`Transaction sent: ${response.txID}`, { variant: 'success' }) + } catch (e) { + enqueueSnackbar('Failed to send transaction', { variant: 'error' }) + } + + setLoading(false) + } + + useEffect(() => { + // async method to init and set subtopia + const initSubtopiaClient = async () => { + if (!activeAddress || !signer) { + return + } + + const client = await SubtopiaClient.init({ + algodClient: algodClient, + productID: DUMMY_SMI_ID, + chainType: ChainType.TESTNET, + registryID: SUBTOPIA_REGISTRY_ID(ChainType.TESTNET), + creator: { + addr: activeAddress, + signer: signer, + }, + }) + + const state = await client.getAppState() + setSubscriptionState(state) + setSubtopiaClient(client) + subscriptionResponse.retry() + } + + if (!subtopiaClient) { + initSubtopiaClient() + } + }) + + return ( + +
+

Subscription status

+
+ {subscriptionState && ( + <> +

{`Title: ${subscriptionState.productName} - ${subscriptionState.subscriptionName}`}

+

{`Subscription type: ${subscriptionState.subType === 0 ? 'Unlimited' : 'Time Based'}`}

+

{`Created at: ${new Date(subscriptionState.createdAt * 1000).toLocaleString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + hour: 'numeric', + minute: 'numeric', + timeZone: 'UTC', + })}`}

+ + )} + <> +

{subscriptionResponse.value ? `Status: Subscribed` : 'Status: Not subscribed'}

+ + +
+ + +
+
+
+ ) +} + +export default Demo diff --git a/template_content/src/components/Transact.tsx.jinja b/template_content/src/components/Transact.tsx.jinja deleted file mode 100644 index 5256774..0000000 --- a/template_content/src/components/Transact.tsx.jinja +++ /dev/null @@ -1,95 +0,0 @@ -import * as algokit from '@algorandfoundation/algokit-utils' -import { useWallet } from '@txnlab/use-wallet' -import algosdk from 'algosdk' -import { useSnackbar } from 'notistack' -import { useState } from 'react' -import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs' - -interface TransactInterface { - openModal: boolean - setModalState: (value: boolean) => void -} - -const Transact = ({ openModal, setModalState }: TransactInterface) => { - const [loading, setLoading] = useState(false) - const [receiverAddress, setReceiverAddress] = useState('') - - const algodConfig = getAlgodConfigFromViteEnvironment() - const algodClient = algokit.getAlgoClient({ - server: algodConfig.server, - port: algodConfig.port, - token: algodConfig.token, - }) - - const { enqueueSnackbar } = useSnackbar() - - const { signer, activeAddress, signTransactions, sendTransactions } = useWallet() - - const handleSubmitAlgo = async () => { - setLoading(true) - - if (!signer || !activeAddress) { - enqueueSnackbar('Please connect wallet first', { variant: 'warning' }) - return - } - - const suggestedParams = await algodClient.getTransactionParams().do() - - const transaction = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ - from: activeAddress, - to: receiverAddress, - amount: 1e6, - suggestedParams, - }) - - const encodedTransaction = algosdk.encodeUnsignedTransaction(transaction) - - const signedTransactions = await signTransactions([encodedTransaction]) - - const waitRoundsToConfirm = 4 - - try { - enqueueSnackbar('Sending transaction...', { variant: 'info' }) - const { id } = await sendTransactions(signedTransactions, waitRoundsToConfirm) - enqueueSnackbar(`Transaction sent: ${id}`, { variant: 'success' }) - setReceiverAddress('') - } catch (e) { - enqueueSnackbar('Failed to send transaction', { variant: 'error' }) - } - - setLoading(false) - } - - return ( - -
-

Send payment transaction

-
- { - setReceiverAddress(e.target.value) - }} - /> -
- - -
-
-
- ) -} - -export default Transact diff --git a/template_content/{% if use_tailwind %}tailwind.config.js{% endif %}.jinja b/template_content/{% if use_tailwind %}tailwind.config.js{% endif %}.jinja index 5d3a44e..4b3d6fb 100644 --- a/template_content/{% if use_tailwind %}tailwind.config.js{% endif %}.jinja +++ b/template_content/{% if use_tailwind %}tailwind.config.js{% endif %}.jinja @@ -6,7 +6,7 @@ module.exports = { }, {% if use_daisy_ui -%} daisyui: { - themes: ['lofi'], + themes: ['dark'], }, plugins: [require('daisyui')], {% endif -%} diff --git a/tests/test_templates.py b/tests/test_templates.py index 6e72e95..678e18f 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -160,6 +160,7 @@ def run_init( file.write_text(content) return result + def test_default_preset(working_dir: Path) -> None: response = run_init( working_dir, diff --git a/tests_generated/test_default_preset/README.md b/tests_generated/test_default_preset/README.md index a28ffbc..4d695f7 100644 --- a/tests_generated/test_default_preset/README.md +++ b/tests_generated/test_default_preset/README.md @@ -1,6 +1,10 @@ # test_default_preset -This starter React project has been generated using AlgoKit. See below for default getting started instructions. +This starter Subtopia integrated dApp project has been generated using AlgoKit. See below for default getting started instructions. + +- For detailed documentation on Subtopia platform refer to [Subtopia documentation](https://docs.subtopia.io). +- To learn more about Subtopia JavaScript SDK refer to [SDK documentation](https://docs.subtopia.io/integrations/javascript-sdk). +- To see other more generic examples in Next.js, React, Vue.js and Svelte refer to [Subtopia examples](https://github.com/subtopia-algo/subtopia-js-examples) # Setup diff --git a/tests_generated/test_default_preset/package.json b/tests_generated/test_default_preset/package.json index 4223cae..db1fa43 100644 --- a/tests_generated/test_default_preset/package.json +++ b/tests_generated/test_default_preset/package.json @@ -10,29 +10,29 @@ "node": ">=18.0" }, "devDependencies": { - "@types/node": "18.17.14", - "@types/react": "18.2.11", - "@types/react-dom": "18.2.4", - "@vitejs/plugin-react-swc": "3.3.2", - "autoprefixer": "10.4.14", - "eslint": "8.42.0", - "eslint-config-prettier": "8.8.0", - "eslint-plugin-prettier": "5.0.0", - "@typescript-eslint/eslint-plugin": "6.5.0", - "@typescript-eslint/parser": "6.5.0", + "@types/node": "^18.17.14", + "@types/react": "^18.2.11", + "@types/react-dom": "^18.2.4", + "@vitejs/plugin-react-swc": "^3.3.2", + "autoprefixer": "^10.4.14", + "eslint": "^8.42.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^5.0.0", + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", "postcss": "^8.4.24", - "tailwindcss": "3.3.2", + "tailwindcss": "^3.3.2", "ts-jest": "^29.1.1", - "@types/jest": "29.5.2", - "ts-node": "10.9.1", - "typescript": "5.1.6", + "@types/jest": "^29.5.2", + "ts-node": "^10.9.1", + "typescript": "^5.1.6", "@playwright/test": "^1.35.0", "playwright": "^1.35.0", - "vite": "4.4.9" + "vite": "^5.0.5" }, "dependencies": { "@walletconnect/modal-sign-html": "^2.6.1", - "@algorandfoundation/algokit-utils": "^4.1.0", + "@algorandfoundation/algokit-utils": "^5.0.1", "@blockshake/defly-connect": "^1.1.6", "@daffiwallet/connect": "^1.0.3", "@perawallet/connect": "^1.3.1", @@ -41,7 +41,9 @@ "daisyui": "^3.1.0", "notistack": "^3.0.1", "react": "^18.2.0", - "react-dom": "18.2.0", + "react-dom": "^18.2.0", + "react-use": "^17.4.2", + "subtopia-js-sdk": "^1.0.0", "tslib": "^2.6.2" }, "scripts": { diff --git a/tests_generated/test_default_preset/src/App.tsx b/tests_generated/test_default_preset/src/App.tsx index 79657e2..a0fab1b 100644 --- a/tests_generated/test_default_preset/src/App.tsx +++ b/tests_generated/test_default_preset/src/App.tsx @@ -6,7 +6,7 @@ import algosdk from 'algosdk' import { SnackbarProvider } from 'notistack' import { useState } from 'react' import ConnectWallet from './components/ConnectWallet' -import Transact from './components/Transact' +import Transact from './components/Demo' import { getAlgodConfigFromViteEnvironment, getKmdConfigFromViteEnvironment } from './utils/network/getAlgoClientConfigs' let providersArray: ProvidersArray @@ -64,34 +64,34 @@ export default function App() { return ( -
-
-
+
+
+

- Welcome to
AlgoKit 🙂
+ Welcome to
Subtopia.io
AlgoKit example

- This starter has been generated using official AlgoKit React template. Refer to the resource below for next steps. + This starter has been generated using Subtopia AlgoKit template. Refer to the resources below for next steps.

Getting started
- {activeAddress && ( - )}
diff --git a/tests_generated/test_default_preset/src/components/Demo.tsx b/tests_generated/test_default_preset/src/components/Demo.tsx new file mode 100644 index 0000000..0a9a214 --- /dev/null +++ b/tests_generated/test_default_preset/src/components/Demo.tsx @@ -0,0 +1,143 @@ +import * as algokit from '@algorandfoundation/algokit-utils' +import { useWallet } from '@txnlab/use-wallet' +import { useSnackbar } from 'notistack' +import { useEffect, useState } from 'react' +import { useAsyncRetry } from 'react-use' +import { ChainType, Duration, ProductState, SUBTOPIA_REGISTRY_ID, SubtopiaClient } from 'subtopia-js-sdk' +import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs' + +interface DemoInterface { + openModal: boolean + setModalState: (value: boolean) => void +} + +const DUMMY_SMI_ID = 481312144 + +const Demo = ({ openModal, setModalState }: DemoInterface) => { + const [loading, setLoading] = useState(false) + + const algodConfig = getAlgodConfigFromViteEnvironment() + const algodClient = algokit.getAlgoClient({ + server: algodConfig.server, + port: algodConfig.port, + token: algodConfig.token, + }) + + const { enqueueSnackbar } = useSnackbar() + const { signer, activeAddress } = useWallet() + const [subtopiaClient, setSubtopiaClient] = useState() + + const subscriptionResponse = useAsyncRetry(async () => { + if (!activeAddress || !subtopiaClient) { + return undefined + } + + return await subtopiaClient.getSubscription({ + algodClient: algodClient, + subscriberAddress: activeAddress, + }) + }, [activeAddress]) + + const [subscriptionState, setSubscriptionState] = useState(undefined) + + const handleSubscribe = async () => { + setLoading(true) + + if (!signer || !activeAddress || !subtopiaClient) { + enqueueSnackbar('Please connect wallet first', { variant: 'warning' }) + return + } + + try { + enqueueSnackbar('Sending transaction...', { variant: 'info' }) + const response = await subtopiaClient + .createSubscription({ + subscriber: { + addr: activeAddress, + signer: signer, + }, + duration: Duration.MONTH, + }) + .catch(() => { + setLoading(false) + }) + + if (!response) { + throw new Error('Failed to send transaction') + } + + subscriptionResponse.retry() + enqueueSnackbar(`Transaction sent: ${response.txID}`, { variant: 'success' }) + } catch (e) { + enqueueSnackbar('Failed to send transaction', { variant: 'error' }) + } + + setLoading(false) + } + + useEffect(() => { + // async method to init and set subtopia + const initSubtopiaClient = async () => { + if (!activeAddress || !signer) { + return + } + + const client = await SubtopiaClient.init({ + algodClient: algodClient, + productID: DUMMY_SMI_ID, + chainType: ChainType.TESTNET, + registryID: SUBTOPIA_REGISTRY_ID(ChainType.TESTNET), + creator: { + addr: activeAddress, + signer: signer, + }, + }) + + const state = await client.getAppState() + setSubscriptionState(state) + setSubtopiaClient(client) + subscriptionResponse.retry() + } + + if (!subtopiaClient) { + initSubtopiaClient() + } + }) + + return ( + +
+

Subscription status

+
+ {subscriptionState && ( + <> +

{`Title: ${subscriptionState.productName} - ${subscriptionState.subscriptionName}`}

+

{`Subscription type: ${subscriptionState.subType === 0 ? 'Unlimited' : 'Time Based'}`}

+

{`Created at: ${new Date(subscriptionState.createdAt * 1000).toLocaleString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + hour: 'numeric', + minute: 'numeric', + timeZone: 'UTC', + })}`}

+ + )} + <> +

{subscriptionResponse.value ? `Status: Subscribed` : 'Status: Not subscribed'}

+ + +
+ + +
+
+
+ ) +} + +export default Demo diff --git a/tests_generated/test_default_preset/src/components/Transact.tsx b/tests_generated/test_default_preset/src/components/Transact.tsx deleted file mode 100644 index 16bd932..0000000 --- a/tests_generated/test_default_preset/src/components/Transact.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import * as algokit from '@algorandfoundation/algokit-utils' -import { useWallet } from '@txnlab/use-wallet' -import algosdk from 'algosdk' -import { useSnackbar } from 'notistack' -import { useState } from 'react' -import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs' - -interface TransactInterface { - openModal: boolean - setModalState: (value: boolean) => void -} - -const Transact = ({ openModal, setModalState }: TransactInterface) => { - const [loading, setLoading] = useState(false) - const [receiverAddress, setReceiverAddress] = useState('') - - const algodConfig = getAlgodConfigFromViteEnvironment() - const algodClient = algokit.getAlgoClient({ - server: algodConfig.server, - port: algodConfig.port, - token: algodConfig.token, - }) - - const { enqueueSnackbar } = useSnackbar() - - const { signer, activeAddress, signTransactions, sendTransactions } = useWallet() - - const handleSubmitAlgo = async () => { - setLoading(true) - - if (!signer || !activeAddress) { - enqueueSnackbar('Please connect wallet first', { variant: 'warning' }) - return - } - - const suggestedParams = await algodClient.getTransactionParams().do() - - const transaction = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ - from: activeAddress, - to: receiverAddress, - amount: 1e6, - suggestedParams, - }) - - const encodedTransaction = algosdk.encodeUnsignedTransaction(transaction) - - const signedTransactions = await signTransactions([encodedTransaction]) - - const waitRoundsToConfirm = 4 - - try { - enqueueSnackbar('Sending transaction...', { variant: 'info' }) - const { id } = await sendTransactions(signedTransactions, waitRoundsToConfirm) - enqueueSnackbar(`Transaction sent: ${id}`, { variant: 'success' }) - setReceiverAddress('') - } catch (e) { - enqueueSnackbar('Failed to send transaction', { variant: 'error' }) - } - - setLoading(false) - } - - return ( - -
-

Send payment transaction

-
- { - setReceiverAddress(e.target.value) - }} - /> -
- - -
-
-
- ) -} - -export default Transact diff --git a/tests_generated/test_default_preset/tailwind.config.js b/tests_generated/test_default_preset/tailwind.config.js index a9f7a95..91c144f 100644 --- a/tests_generated/test_default_preset/tailwind.config.js +++ b/tests_generated/test_default_preset/tailwind.config.js @@ -5,7 +5,7 @@ module.exports = { extend: {}, }, daisyui: { - themes: ['lofi'], + themes: ['dark'], }, plugins: [require('daisyui')], }