diff --git a/src/app.ts b/src/app.ts index 41d598518..375c74f0c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,13 +1,12 @@ import path from 'path'; -import {createProject, runDepsInstall} from './make'; -import {promptAndGetConfig,} from './user-input'; +import { createProject, runDepsInstall } from './make'; +import { promptAndGetConfig, } from './user-input'; import * as show from './messages'; (async function () { - const promptResult = await promptAndGetConfig(); - if (promptResult === null) { - return; - } + const prompt = await promptAndGetConfig(); + if (prompt === undefined) return; + const { config: { projectName, @@ -17,7 +16,7 @@ import * as show from './messages'; install, }, projectPath, - } = promptResult; + } = prompt; show.creatingApp(); @@ -28,22 +27,18 @@ import * as show from './messages'; frontend, tests, projectName, - verbose: false, - rootDir: path.resolve(__dirname, '../templates'), + templatesDir: path.resolve(__dirname, '../templates'), projectPath, }); } catch (e) { console.error(e); createSuccess = false; } - if (install) { - await runDepsInstall(projectPath); - } if (createSuccess) { + install && await runDepsInstall(projectPath); show.setupSuccess(projectName, contract, frontend, install); } else { - show.setupFailed(); - return; + return show.setupFailed(); } })(); \ No newline at end of file diff --git a/src/make.ts b/src/make.ts index 417853c42..028ff419c 100644 --- a/src/make.ts +++ b/src/make.ts @@ -1,113 +1,71 @@ -import {CreateProjectParams} from './types'; +import { CreateContractParams, CreateGatewayParams } from './types'; import * as show from './messages'; import spawn from 'cross-spawn'; import fs from 'fs'; -import {ncp} from 'ncp'; +import { ncp } from 'ncp'; import path from 'path'; -import {buildPackageJson} from './package-json'; -export async function createProject({contract, frontend, tests, projectPath, projectName, verbose, rootDir}: CreateProjectParams): Promise { - - // Create files in the project folder - await createFiles({contract, frontend, projectName, tests, projectPath, verbose, rootDir}); +export async function createProject({ contract, frontend, tests, projectPath, projectName, templatesDir }: CreateContractParams & CreateGatewayParams): Promise { - // Create package.json - const packageJson = buildPackageJson({contract, frontend, tests, projectName}); - fs.writeFileSync(path.resolve(projectPath, 'package.json'), Buffer.from(JSON.stringify(packageJson, null, 2))); + if(contract !== 'none'){ + await createContract({ contract, tests, projectPath, projectName, templatesDir }); + }else{ + await createGateway({ frontend, projectPath, projectName, templatesDir }); + } return true; } -export async function createFiles({contract, frontend, tests, projectPath, verbose, rootDir}: CreateProjectParams) { - // skip build artifacts and symlinks - const skip = ['.cache', 'dist', 'out', 'node_modules']; +async function createContract({ contract, tests, projectPath, projectName, templatesDir }: CreateContractParams) { + // contract folder + const sourceContractDir = path.resolve(templatesDir, 'contracts', contract); + const targetContractDir = projectPath; + fs.mkdirSync(targetContractDir, { recursive: true }); + await copyDir(sourceContractDir, targetContractDir); - // copy frontend - if (frontend !== 'none') { - const sourceFrontendDir = path.resolve(`${rootDir}/frontend/${frontend}`); - const targetFrontendDir = path.resolve(`${projectPath}/frontend`); - fs.mkdirSync(targetFrontendDir, { recursive: true }); - await copyDir(sourceFrontendDir, targetFrontendDir, {verbose, skip: skip.map(f => path.join(sourceFrontendDir, f))}); - } + // copy sandbox-test dir + const targetTestDir = path.resolve(projectPath, `sandbox-${tests}`); + const sourceTestDir = path.resolve(`${templatesDir}/sandbox-tests/sandbox-${tests}`); - // shared files - const sourceSharedDir = path.resolve(rootDir, 'shared'); - await copyDir(sourceSharedDir, projectPath, {verbose, skip: skip.map(f => path.join(sourceSharedDir, f))}); + fs.mkdirSync(targetTestDir); + await copyDir(sourceTestDir, targetTestDir); - // copy contract files - if(contract !== 'none') { - const sourceContractDir = path.resolve(rootDir, 'contracts', contract); - const targetContractDir = path.resolve(projectPath, 'contract'); - fs.mkdirSync(targetContractDir, { recursive: true }); - await copyDir(sourceContractDir, targetContractDir, { - verbose, - skip: skip.map(f => path.join(sourceContractDir, f)) - }); - } - // tests dir - if(contract !== 'none') { - const targetTestDir = path.resolve(projectPath, 'integration-tests'); - fs.mkdirSync(targetTestDir, { recursive: true }); + if (contract === 'rs'){ + if (tests === 'rs') { + // leave only one test script + fs.unlinkSync(path.resolve(projectPath, 'test-ts.sh')); + fs.renameSync(path.resolve(projectPath, 'test-rs.sh'), path.resolve(projectPath, 'test.sh')); - // copy tests - shared files - const sourceTestSharedDir = path.resolve(`${rootDir}/integration-tests/${tests}-tests`); - await copyDir(sourceTestSharedDir, targetTestDir, { - verbose, - skip: skip.map(f => path.join(sourceTestSharedDir, f)) - }); + // add workspace to Cargo.toml + const cargoTomlPath = path.resolve(projectPath, 'Cargo.toml'); + const cargoToml = fs.readFileSync(cargoTomlPath).toString(); + const cargoTomlWithWorkspace = cargoToml + '\n[workspace]\nmembers = ["sandbox-rs"]'; + fs.writeFileSync(cargoTomlPath, cargoTomlWithWorkspace); + }else{ + // leave only one test script + fs.unlinkSync(path.resolve(projectPath, 'test-rs.sh')); + fs.renameSync(path.resolve(projectPath, 'test-ts.sh'), path.resolve(projectPath, 'test.sh')); + } } - - // add .gitignore - await renameFile(`${projectPath}/template.gitignore`, `${projectPath}/.gitignore`); } -export const renameFile = async function (oldPath: string, newPath: string) { - return new Promise((resolve, reject) => { - fs.rename(oldPath, newPath, err => { - if (err) { - console.error(err); - reject(err); - return; - } - resolve(); - }); - }); -}; +async function createGateway({ frontend, projectPath, projectName, templatesDir }: CreateGatewayParams) { + const sourceFrontendDir = path.resolve(`${templatesDir}/frontend/${frontend}`); + const targetFrontendDir = path.resolve(`${projectPath}`); + fs.mkdirSync(targetFrontendDir, { recursive: true }); + await copyDir(sourceFrontendDir, targetFrontendDir); +} // Wrap `ncp` tool to wait for the copy to finish when using `await` -// Allow passing `skip` variable to skip copying an array of filenames -export function copyDir(source: string, dest: string, {skip, verbose}: {skip: string[], verbose: boolean}) { +export function copyDir(source: string, dest: string) { return new Promise((resolve, reject) => { - const copied: string[] = []; - const skipped: string[] = []; - const filter = skip && function(filename: string) { - const shouldCopy = !skip.find(f => filename.includes(f)); - shouldCopy ? copied.push(filename) : skipped.push(filename); - return !skip.find(f => filename.includes(f)); - }; - - ncp(source, dest, {filter}, err => { - if (err) { - reject(err); - return; - } - - if (verbose) { - console.log('Copied:'); - copied.forEach(f => console.log(' ' + f)); - console.log('Skipped:'); - skipped.forEach(f => console.log(' ' + f)); - } - - resolve(); - }); + ncp(source, dest, { }, err => err? reject(err): resolve()); }); } export async function runDepsInstall(projectPath: string) { show.depsInstall(); - const npmCommandArgs = ['install']; - await new Promise((resolve, reject) => spawn('npm', npmCommandArgs, { + await new Promise((resolve, reject) => spawn('npm', ['install'], { cwd: projectPath, stdio: 'inherit', }).on('close', (code: number) => { diff --git a/src/messages.ts b/src/messages.ts index 1e2d3c8a9..979e67e53 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -8,44 +8,52 @@ if (process.env.NEAR_NO_COLOR) { export const show = (...args: unknown[]) => console.log(...args); -export const welcome = () => show(chalk`{blue ======================================================} -👋 {bold {green Welcome to NEAR!}} Learn more: https://docs.near.org/ -🔧 Let's get your dApp ready. +export const welcome = () => show(chalk` {blue ======================================================} -(${trackingMessage}) -`); +👋 {bold {green Welcome to Near!}} Learn more: https://docs.near.org/ +🔧 Let's get your project ready. +{blue ======================================================} +(${trackingMessage})`); export const setupFailed = () => show(chalk`{bold {red ==========================================}} -{red ⛔️ There was a problem during NEAR project setup}. +{red ⛔️ There was a problem during the project setup}. Please refer to https://github.com/near/create-near-app README to troubleshoot. Notice: some platforms aren't supported (yet). {bold {red ==========================================}}`); -export const successContractToText = (contract: Contract) => contract === 'none' ? '' : chalk`with a smart contract in {bold ${contract === 'rust' ? 'Rust' : 'JavaScript'}}`; -export const successFrontendToText = (frontend: Frontend) => frontend === 'none' ? '' : chalk` and a frontend template${frontend === 'gateway' ? chalk`{bold in React.js}` : ''}`; +export const successContractToText = (contract: Contract) => contract === 'none' ? '' : chalk`a smart contract in {bold ${contract === 'rs' ? 'Rust' : 'Typescript'}}`; +export const successFrontendToText = (frontend: Frontend) => frontend === 'none' ? '' : chalk`a gateway using ${frontend === 'next' ? 'NextJS + React' : 'Vanilla-JS'}`; export const setupSuccess = (projectName: ProjectName, contract: Contract, frontend: Frontend, install: boolean) => show(chalk` {green ======================================================} -✅ Success! Created '${projectName}' - ${successContractToText(contract)}${successFrontendToText(frontend)}. -${contract === 'rust' ? chalk`🦀 If you are new to Rust please visit {bold {green https://www.rust-lang.org }}\n` : ''} - {bold {bgYellow {black Your next steps}}}: +✅ Success! Created '${projectName}', ${successContractToText(contract)}${successFrontendToText(frontend)}. +${contract === 'rs' ? chalk`🦀 If you are new to Rust please visit {bold {green https://www.rust-lang.org }}\n` : ''} +{bold {bgYellow {black Next steps}}}: +${contractInstructions(projectName, contract, install)}${gatewayInstructions(projectName, frontend, install)}`); + +export const contractInstructions = (projectName: ProjectName, contract: Contract, install: boolean) => contract === 'none' ? '' : chalk` - {inverse Navigate to your project}: {blue cd {bold ${projectName}}} - ${!install ? chalk`- {inverse Install all dependencies} +${contract ==='ts' && !install ? chalk` - {inverse Install all dependencies} {blue npm {bold install}}` : 'Then:'} - {inverse Build your contract}: - {blue npm {bold run build}} - - {inverse Test your contract} in NEAR SandBox: - {blue npm {bold test}} + ${contract === 'ts' ? chalk`{blue npm {bold run build}}` : chalk`{blue {bold ./build.sh}}`} + - {inverse Test your contract} in the Sandbox: + ${contract === 'ts' ? chalk`{blue npm {bold run test}}` : chalk`{blue {bold ./test.sh}}`} - {inverse Deploy your contract} to NEAR TestNet with a temporary dev account: - {blue npm {bold run deploy}} - ${frontend !== 'none' ? chalk`- {inverse Start your frontend}: - {blue npm {bold start}}\n` : ''} -🧠 Read {bold {greenBright README.md}} to explore further.`); + ${contract === 'ts' ? chalk`{blue npm {bold run deploy}}` : chalk`{blue {bold ./deploy.sh}}`} +🧠 Read {bold {greenBright README.md}} to explore further`; + +export const gatewayInstructions = (projectName: ProjectName, frontend: Frontend, install: boolean) => frontend === 'none' ? '' : chalk` + - {inverse Navigate to your project}: + {blue cd {bold ${projectName}}} +${!install ? chalk` - {inverse Install all dependencies} + {blue pnpm {bold install}}` : 'Then:'} + - {inverse Start your app}: + {blue pnpm {bold run dev}}`; export const argsError = () => show(chalk`{red Arguments error} Run {blue npx create-near-app} without arguments, or use: -npx create-near-app --contract rust|js --frontend react|vanilla|none --tests js|rust`); +npx create-near-app --frontend next|vanilla|none --contract rs|ts|none --tests rs|ts|none`); export const unsupportedNodeVersion = (supported: string) => show(chalk`{red We support node.js version ${supported} or later}`); diff --git a/src/package-json.ts b/src/package-json.ts deleted file mode 100644 index 8e1c0d139..000000000 --- a/src/package-json.ts +++ /dev/null @@ -1,133 +0,0 @@ -import {Contract, CreateProjectParams, TestingFramework} from './types'; - -type Entries = Record; -type PackageBuildParams = Pick; - -export function buildPackageJson({contract, frontend, tests, projectName}: PackageBuildParams): Entries { - const result = basePackage({ - contract, frontend, tests, projectName, - }); - return result; -} - -function basePackage({contract, frontend, tests, projectName}: PackageBuildParams): Entries { - const hasFrontend = frontend !== 'none'; - return { - 'name': projectName, - 'version': '1.0.0', - 'license': '(MIT AND Apache-2.0)', - 'scripts': { - ...startScript(hasFrontend), - ...deployScript(contract), - ...buildScript(hasFrontend), - ...buildContractScript(contract), - 'test': 'npm run test:unit && npm run test:integration', - ...unitTestScripts(contract), - ...integrationTestScripts(contract, tests), - ...npmInstallScript(contract, hasFrontend, tests), - }, - 'devDependencies': { - 'near-cli': '^3.3.0', - }, - 'dependencies': {} - }; -} - -const startScript = (hasFrontend: boolean): Entries => hasFrontend ? { - 'start': 'cd frontend && npm run dev' -} : {}; - -const buildScript = (hasFrontend: boolean): Entries => hasFrontend ? { - 'build': 'npm run build:contract && npm run build:web', - 'build:web': 'cd frontend && npm run build', -} : { - 'build': 'npm run build:contract', -}; - -const buildContractScriptName = 'build:contract'; - -const buildContractScript = (contract: Contract): Entries => { - switch (contract) { - case 'js': - return { - [buildContractScriptName]: 'cd contract && npm run build', - }; - case 'rust': - return { - [buildContractScriptName]: 'cd contract && ./build.sh', - }; - case 'none': - return { - [buildContractScriptName]: 'echo "No contract to build"', - }; - } -}; - -const deployScript = (contract: Contract): Entries => { - switch (contract) { - case 'js': - return { - 'deploy': 'cd contract && npm run deploy', - }; - case 'rust': - return { - 'deploy': 'cd contract && ./deploy.sh', - }; - case 'none': - return { - 'deploy': 'echo "No contract to deploy"', - }; - } -}; - -const unitTestScripts = (contract: Contract): Entries => { - switch (contract) { - case 'js': - return {'test:unit': 'cd contract && npm test'}; - case 'rust': - return {'test:unit': 'cd contract && cargo test'}; - case 'none': - return {'test:unit': 'echo "No contract to test"'}; - } -}; - -const integrationTestScripts = (contract: Contract, tests: TestingFramework): Entries => { - if (contract === 'none') { - return {'test:integration': 'echo "No contract to test"'}; - } - - let wasm_path: String = ''; - switch (contract) { - case 'js': wasm_path = 'contract/build/hello_near.wasm'; break; - case 'rust': wasm_path = 'contract/target/wasm32-unknown-unknown/release/hello_near.wasm'; break; - } - - let run_test: String = ''; - switch(tests){ - case 'js': run_test = `npm test -- -- "./${wasm_path}"`; break; - case 'rust': run_test =`cargo run --example integration-tests "../${wasm_path}"`; break; - } - - return { - 'test:integration': `npm run ${buildContractScriptName} && cd integration-tests && ${run_test}`, - }; -}; - -const npmInstallScript = (contract: Contract, hasFrontend: boolean, tests: TestingFramework): Entries => { - const frontend_install = hasFrontend? 'cd frontend && npm install && cd ..' : 'echo no frontend'; - const test_install = (tests === 'js')? 'cd integration-tests && npm install && cd ..' : 'echo rs tests'; - - let contract_install = ''; - switch (contract) { - case 'js': - contract_install = 'cd contract && npm install'; break; - case 'rust': - contract_install = 'echo rs contract'; break; - case 'none': - contract_install = 'echo no contract'; break; - } - - return { - 'postinstall': `${frontend_install} && ${test_install} && ${contract_install}` - }; -}; diff --git a/src/tracking.ts b/src/tracking.ts index 9d3f7c194..fd95e2183 100644 --- a/src/tracking.ts +++ b/src/tracking.ts @@ -6,7 +6,7 @@ const MIXPANEL_TOKEN = 'df164f13212cbb0dfdae991da60e87f2'; const tracker = mixpanel.init(MIXPANEL_TOKEN); -export const trackingMessage = chalk`NEAR collects anonymous information on the commands used. No personal information that could identify you is shared`; +export const trackingMessage = chalk`Near collects anonymous information on the commands used. No personal information that could identify you is shared`; // TODO: track different failures & install usage export const trackUsage = async (frontend: Frontend, contract: Contract) => { diff --git a/src/types.ts b/src/types.ts index b3dba64e9..bcbae450a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,11 @@ -export type Contract = 'js' | 'rust' | 'none'; -export const CONTRACTS: Contract[] = ['js', 'rust']; -export type Frontend = 'gateway' | 'vanilla' | 'none'; -export const FRONTENDS: Frontend[] = ['gateway', 'vanilla', 'none']; -export type TestingFramework = 'rust' | 'js'; -export const TESTING_FRAMEWORKS: TestingFramework[] = ['rust', 'js']; +export type Contract = 'ts' | 'rs' | 'none'; +export const CONTRACTS: Contract[] = ['ts', 'rs', 'none']; +export type Frontend = 'next' | 'vanilla' | 'none'; +export const FRONTENDS: Frontend[] = ['next', 'vanilla', 'none']; +export type TestingFramework = 'rs' | 'ts' | 'none'; +export const TESTING_FRAMEWORKS: TestingFramework[] = ['rs', 'ts', 'none']; +export type App = 'contract' | 'gateway'; +export const APPS: App[] = ['contract', 'gateway']; export type ProjectName = string; export interface UserConfig { contract: Contract; @@ -12,12 +14,18 @@ export interface UserConfig { tests: TestingFramework; install: boolean; } -export type CreateProjectParams = { + +export type CreateContractParams = { contract: Contract, - frontend: Frontend, tests: TestingFramework, projectPath: string, projectName: ProjectName, - verbose: boolean, - rootDir: string, + templatesDir: string, +} + +export type CreateGatewayParams = { + frontend: Frontend, + projectPath: string, + projectName: ProjectName, + templatesDir: string, } \ No newline at end of file diff --git a/src/user-input.ts b/src/user-input.ts index ac53c83aa..bfa79d85f 100644 --- a/src/user-input.ts +++ b/src/user-input.ts @@ -1,4 +1,5 @@ import { + App, Contract, CONTRACTS, Frontend, @@ -9,35 +10,35 @@ import { UserConfig } from './types'; import chalk from 'chalk'; -import prompt, {PromptObject} from 'prompts'; -import {program} from 'commander'; +import prompt, { PromptObject } from 'prompts'; +import { program } from 'commander'; import * as show from './messages'; import semver from 'semver'; -import {trackUsage} from './tracking'; +import { trackUsage } from './tracking'; import fs from 'fs'; export async function getUserArgs(): Promise { program .argument('[projectName]') - .option('--contract ') - .option('--frontend ') - .option('--tests ') - .option('--install'); - + .option('--contract [ts|rs|none]') + .option('--frontend [next|vanilla|none]') + .option('--tests [rs|ts|none]') + .option('--install') + .addHelpText('after', 'You can create a frontend or a contract with tests'); program.parse(); const options = program.opts(); const [projectName] = program.args; - const {contract, frontend, tests, install} = options; - return {contract, frontend, projectName, tests, install}; + const { contract, frontend, tests, install } = options; + return { contract, frontend, projectName, tests, install }; } export function validateUserArgs(args: UserConfig): 'error' | 'ok' | 'none' { if (args === null) { return 'error'; } - const {projectName, contract, frontend, tests} = args; + const { projectName, contract, frontend, tests } = args; const hasAllOptions = contract !== undefined && frontend !== undefined; const hasPartialOptions = contract !== undefined || frontend !== undefined; const hasProjectName = projectName !== undefined; @@ -57,125 +58,129 @@ export function validateUserArgs(args: UserConfig): 'error' | 'ok' | 'none' { } } -type Choices = {title: string, description?: string, value: T}[]; +type Choices = { title: string, description?: string, value: T }[]; + +const appChoices: Choices = [ + { title: 'A Near Smart Contract', description: 'A smart contract to be deployed in the Near Blockchain', value: 'contract' }, + { title: 'A Near Gateway (Web App)', description: 'A multi-chain App that talks with Near contracts and Near components', value: 'gateway' }, +]; const contractChoices: Choices = [ - {title: 'Yes, in TypeScript', description: 'Build a Near contract using javascript/typescript', value: 'js'}, - {title: 'Yes, in Rust', description: 'Build a Near contract using Rust' , value: 'rust'}, - {title: 'No', description: 'You are not building a Near smart contract' , value: 'none'}, + { title: 'JS/TS Contract', description: 'A Near contract written in javascript/typescript', value: 'ts' }, + { title: 'Rust Contract', description: 'A Near contract written in Rust', value: 'rs' }, ]; const testsChoices: Choices = [ - {title: 'Tests written in Rust', value: 'rust'}, - {title: 'Tests written in Javascript', value: 'js'}, + { title: 'Tests written in Rust', value: 'rs' }, + { title: 'Tests written in Typescript', value: 'ts' }, ]; const frontendChoices: Choices = [ - {title: 'Composable web app (Gateway)' , description:'Leverage next.js and web3 components to create multi-chain apps', value: 'gateway'}, - {title: 'Vanilla web app', description:'Interact with the Near blockchain using a simple web app' , value: 'vanilla'}, - {title: 'No frontend', description:'Build a smart contract with no frontend', value: 'none'}, + { title: 'NextJs + React', description: 'A composable app built using Next.js, React and Near components', value: 'next' }, + { title: 'Vanilla JS', description: 'A framework-less web app with limited capabilities.', value: 'vanilla' }, ]; -const userPrompts: PromptObject[] = [ - { - type: 'select', - name: 'frontend', - message: 'Frontend: What kind of App are you building?', - choices: frontendChoices, - }, + +const appPrompt: PromptObject = { + type: 'select', + name: 'app', + message: 'What do you want to build?', + choices: appChoices, +}; + +const frontendPrompt: PromptObject = { + type: 'select', + name: 'frontend', + message: 'Select a framework for your frontend (Gateway)', + choices: frontendChoices, +}; + +const contractPrompt: PromptObject[] = [ { type: 'select', name: 'contract', - message: 'Contract: Are you building a NEAR contract?', + message: 'Select a smart contract template for your project', choices: contractChoices, }, { - type: prev => prev === 'rust' ? 'select' : null, + type: prev => prev === 'rs' ? 'select' : null, name: 'tests', message: 'Sandbox Testing: Which language do you prefer to test your contract?', choices: testsChoices, - }, - { - type: 'text', - name: 'projectName', - message: 'Name your project (this will create a directory with that name)', - initial: 'hello-near', - }, - { - type: 'confirm', - name: 'install', - message: chalk`Run {bold {blue 'npm install'}} now?`, - initial: true, - }, + } ]; -export async function getUserAnswers() { - const answers = await prompt(userPrompts); - if (!answers.tests) { - answers.tests = answers.contract !== 'rust' ? 'js' : 'rust'; +const namePrompts: PromptObject = { + type: 'text', + name: 'projectName', + message: 'Name your project (we will create a directory with that name)', + initial: 'hello-near', +}; + +const npmPrompt: PromptObject = { + type: 'confirm', + name: 'install', + message: chalk`Run {bold {blue 'npm install'}} now?`, + initial: true, +}; + +const promptUser = async (prompts: PromptObject | PromptObject[]): Promise> => { + // Prompt, and exit if user cancels + return prompt(prompts, { onCancel: () => process.exit(0) }); +}; + +export async function getUserAnswers(): Promise { + // Either the user wants a gateway or a contract + const { app } = await promptUser(appPrompt); + + if (app === 'gateway') { + // If gateway, ask for the framework to use + const { frontend, projectName, install } = await promptUser([frontendPrompt, namePrompts, npmPrompt]); + return { frontend, contract: 'none', tests: 'none', projectName, install }; + } else { + // If contract, ask for the language for the contract and tests + let {contract, tests} = await promptUser(contractPrompt); + tests = contract === 'ts'? 'ts' : tests; + const { projectName } = await promptUser(namePrompts); + const install = contract === 'ts' ? (await promptUser(npmPrompt)).install as boolean : false; + return { frontend: 'none', contract, tests, projectName, install }; } - return answers; } -export async function showProjectNamePrompt() { - const [, , , projectName] = userPrompts; - const answers = await prompt([projectName]); - return answers; -} - -export function userAnswersAreValid(answers: Partial): answers is UserConfig { - const {contract, frontend, projectName, tests} = answers; - if ([contract, frontend, projectName, tests].includes(undefined)) { - return false; - } else { - return true; +export async function promptAndGetConfig(): Promise<{ config: UserConfig, projectPath: string } | void> { + const supportedNodeVersion = require('../package.json').engines.node; + if (!semver.satisfies(process.version, supportedNodeVersion)) { + return show.unsupportedNodeVersion(supportedNodeVersion); } -} -export async function promptAndGetConfig(): Promise<{ config: UserConfig, projectPath: string, isFromPrompts: boolean } | null> { - let config: UserConfig | null = null; - let isFromPrompts = false; - // process cli args - const args = await getUserArgs(); - const argsValid = validateUserArgs(args); - if (argsValid === 'error') { - show.argsError(); - return null; - } else if (argsValid === 'ok') { - config = args as UserConfig; + if (process.platform === 'win32') { + return show.windowsWarning(); } - show.welcome(); + // process cli args + let args = await getUserArgs(); - const nodeVersion = process.version; - const supportedNodeVersion = require('../package.json').engines.node; - if (!semver.satisfies(nodeVersion, supportedNodeVersion)) { - show.unsupportedNodeVersion(supportedNodeVersion); - // TODO: track unsupported versions - return null; + if( args.contract && (!args.tests || args.frontend) ){ + return show.argsError(); } - if (process.platform === 'win32') { - // TODO: track windows - show.windowsWarning(); - return null; + if( args.frontend && (args.tests || args.contract) ){ + return show.argsError(); } - // Get user input - if (config === null) { - const userInput = await getUserAnswers(); - isFromPrompts = true; - if (!userAnswersAreValid(userInput)) { - throw new Error(`Invalid prompt. ${JSON.stringify(userInput)}`); - } - config = userInput; + // If no args, prompt user + if( !args.contract && !args.frontend ){ + show.welcome(); + args = await getUserAnswers(); } - const {frontend, contract} = config as UserConfig; + + // track user input + const { frontend, contract } = args; trackUsage(frontend, contract); - let path = projectPath(config.projectName); - // If dir exists warn and exit + let path = projectPath(args.projectName); + if (fs.existsSync(path)) { - show.directoryExists(path); - return null; + return show.directoryExists(path); } - return {config, projectPath: path, isFromPrompts}; + + return { config: args, projectPath: path }; } export const projectPath = (projectName: ProjectName) => `${process.cwd()}/${projectName}`; diff --git a/templates/contracts/js/build.sh b/templates/contracts/js/build.sh deleted file mode 100755 index e3a278622..000000000 --- a/templates/contracts/js/build.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -echo ">> Building contract" - -near-sdk-js build src/contract.ts build/hello_near.wasm diff --git a/templates/contracts/js/deploy.sh b/templates/contracts/js/deploy.sh deleted file mode 100755 index 404cb2517..000000000 --- a/templates/contracts/js/deploy.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -./build.sh - -if [ $? -ne 0 ]; then - echo ">> Error building contract" - exit 1 -fi - -echo ">> Deploying contract" - -# https://docs.near.org/tools/near-cli#near-dev-deploy -near dev-deploy --wasmFile build/hello_near.wasm - -# if ../frontend folder exists, copy the env file there -if [ -d "../frontend" ]; then - (echo ; cat ./neardev/dev-account.env) >> ../frontend/.env -fi \ No newline at end of file diff --git a/templates/contracts/js/package.json b/templates/contracts/js/package.json deleted file mode 100644 index 8c4dcdc85..000000000 --- a/templates/contracts/js/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "hello_near", - "version": "1.0.0", - "license": "(MIT AND Apache-2.0)", - "type": "module", - "scripts": { - "build": "./build.sh", - "deploy": "./deploy.sh", - "test": "echo use integration-tests" - }, - "dependencies": { - "near-cli": "^3.4.2", - "near-sdk-js": "1.0.0" - }, - "devDependencies": { - "typescript": "^4.7.4", - "ts-morph": "^16.0.0" - } -} diff --git a/templates/contracts/rust/.cargo/config b/templates/contracts/rs/.cargo/config similarity index 100% rename from templates/contracts/rust/.cargo/config rename to templates/contracts/rs/.cargo/config diff --git a/templates/contracts/rust/.gitignore b/templates/contracts/rs/.gitignore similarity index 100% rename from templates/contracts/rust/.gitignore rename to templates/contracts/rs/.gitignore diff --git a/templates/contracts/rust/Cargo.toml b/templates/contracts/rs/Cargo.toml similarity index 92% rename from templates/contracts/rust/Cargo.toml rename to templates/contracts/rs/Cargo.toml index 23ac495ec..222ddd6de 100644 --- a/templates/contracts/rust/Cargo.toml +++ b/templates/contracts/rs/Cargo.toml @@ -18,6 +18,3 @@ lto = true debug = false panic = "abort" overflow-checks = true - -[workspace] -members = [] diff --git a/templates/contracts/rust/README.md b/templates/contracts/rs/README.md similarity index 71% rename from templates/contracts/rust/README.md rename to templates/contracts/rs/README.md index 7b0ce0f7f..b8a2ed3a7 100644 --- a/templates/contracts/rust/README.md +++ b/templates/contracts/rs/README.md @@ -42,14 +42,23 @@ impl Contract {
-## 1. Build and Deploy the Contract -You can automatically compile and deploy the contract in the NEAR testnet by running: +## 1. Build, Test and Deploy +To build the contract you can execute the `./build.sh` script, which will in turn run: ```bash -./deploy.sh +rustup target add wasm32-unknown-unknown +cargo build --target wasm32-unknown-unknown --release ``` -Once finished, check the `neardev/dev-account` file to find the address in which the contract was deployed: +Then, run the `./deploy.sh` script, which will in turn run: + +```bash +near dev-deploy --wasmFile ./target/wasm32-unknown-unknown/release/hello_near.wasm +``` + +the command [`near dev-deploy`](https://docs.near.org/tools/near-cli#near-dev-deploy) automatically creates an account in the NEAR testnet, and deploys the compiled contract on it. + +Once finished, check the `./neardev/dev-account` file to find the address in which the contract was deployed: ```bash cat ./neardev/dev-account @@ -74,7 +83,7 @@ near view get_greeting ## 3. Store a New Greeting `set_greeting` changes the contract's state, for which it is a `change` method. -`Change` methods can only be invoked using a NEAR account, since the account needs to pay GAS for the transaction. +`Change` methods can only be invoked using a NEAR account, since the account needs to pay GAS for the transaction. In this case, we are asking the account we created in step 1 to sign the transaction. ```bash # Use near-cli to set a new greeting diff --git a/templates/contracts/rs/build.sh b/templates/contracts/rs/build.sh new file mode 100755 index 000000000..243f2da61 --- /dev/null +++ b/templates/contracts/rs/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh +rustup target add wasm32-unknown-unknown +cargo build --target wasm32-unknown-unknown --release \ No newline at end of file diff --git a/templates/contracts/rs/deploy.sh b/templates/contracts/rs/deploy.sh new file mode 100755 index 000000000..3ebf84984 --- /dev/null +++ b/templates/contracts/rs/deploy.sh @@ -0,0 +1,2 @@ +#!/bin/sh +near dev-deploy --wasmFile ./target/wasm32-unknown-unknown/release/hello_near.wasm \ No newline at end of file diff --git a/templates/contracts/rust/rust-toolchain.toml b/templates/contracts/rs/rust-toolchain.toml similarity index 100% rename from templates/contracts/rust/rust-toolchain.toml rename to templates/contracts/rs/rust-toolchain.toml diff --git a/templates/contracts/rust/src/lib.rs b/templates/contracts/rs/src/lib.rs similarity index 88% rename from templates/contracts/rust/src/lib.rs rename to templates/contracts/rs/src/lib.rs index f0b992738..afb9e74c0 100644 --- a/templates/contracts/rust/src/lib.rs +++ b/templates/contracts/rs/src/lib.rs @@ -1,9 +1,7 @@ // Find all our documentation at https://docs.near.org use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; -use near_sdk::{log, near_bindgen}; - -// Define the default greeting -const DEFAULT_GREETING: &str = "Hello"; +use near_sdk::env::log_str; +use near_sdk::near_bindgen; // Define the contract structure #[near_bindgen] @@ -15,7 +13,7 @@ pub struct Contract { // Define the default, which automatically initializes the contract impl Default for Contract { fn default() -> Self { - Self { greeting: DEFAULT_GREETING.to_string() } + Self { greeting: "Hello".to_string() } } } @@ -29,7 +27,7 @@ impl Contract { // Public method - accepts a greeting, such as "howdy", and records it pub fn set_greeting(&mut self, greeting: String) { - log!("Saving greeting {}", greeting); + log_str(&format!("Saving greeting: {greeting}")); self.greeting = greeting; } } diff --git a/templates/contracts/rs/test-rs.sh b/templates/contracts/rs/test-rs.sh new file mode 100755 index 000000000..18014b597 --- /dev/null +++ b/templates/contracts/rs/test-rs.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# unit testing +cargo test + +# sandbox testing +./build.sh +cd sandbox-rs +cargo run --example sandbox "../target/wasm32-unknown-unknown/release/hello_near.wasm" \ No newline at end of file diff --git a/templates/contracts/rs/test-ts.sh b/templates/contracts/rs/test-ts.sh new file mode 100755 index 000000000..6fb65c585 --- /dev/null +++ b/templates/contracts/rs/test-ts.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# unit testing +cargo test + +# sandbox testing +./build.sh +cd sandbox-ts +npm i +npm run test -- -- "../target/wasm32-unknown-unknown/release/hello_near.wasm" \ No newline at end of file diff --git a/templates/contracts/rust/build.sh b/templates/contracts/rust/build.sh deleted file mode 100755 index 9bf74a26d..000000000 --- a/templates/contracts/rust/build.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -echo ">> Building contract" - -rustup target add wasm32-unknown-unknown -cargo build --all --target wasm32-unknown-unknown --release diff --git a/templates/contracts/rust/deploy.sh b/templates/contracts/rust/deploy.sh deleted file mode 100755 index dbe0fe948..000000000 --- a/templates/contracts/rust/deploy.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -./build.sh - -if [ $? -ne 0 ]; then - echo ">> Error building contract" - exit 1 -fi - -echo ">> Deploying contract" - -# https://docs.near.org/tools/near-cli#near-dev-deploy -near dev-deploy --wasmFile ./target/wasm32-unknown-unknown/release/hello_near.wasm - -# if ../frontend folder exists, copy the env file there -if [ -d "../frontend" ]; then - (echo ; cat ./neardev/dev-account.env) >> ../frontend/.env -fi \ No newline at end of file diff --git a/templates/contracts/js/.gitignore b/templates/contracts/ts/.gitignore similarity index 100% rename from templates/contracts/js/.gitignore rename to templates/contracts/ts/.gitignore diff --git a/templates/contracts/js/README.md b/templates/contracts/ts/README.md similarity index 99% rename from templates/contracts/js/README.md rename to templates/contracts/ts/README.md index a360f85b4..cdabf58dd 100644 --- a/templates/contracts/js/README.md +++ b/templates/contracts/ts/README.md @@ -34,6 +34,7 @@ class HelloNear { You can automatically compile and deploy the contract in the NEAR testnet by running: ```bash +npm run build npm run deploy ``` diff --git a/templates/contracts/js/package-lock.json b/templates/contracts/ts/package-lock.json similarity index 100% rename from templates/contracts/js/package-lock.json rename to templates/contracts/ts/package-lock.json diff --git a/templates/contracts/ts/package.json b/templates/contracts/ts/package.json new file mode 100644 index 000000000..77a0698aa --- /dev/null +++ b/templates/contracts/ts/package.json @@ -0,0 +1,20 @@ +{ + "name": "hello_near", + "version": "1.0.0", + "license": "(MIT AND Apache-2.0)", + "type": "module", + "scripts": { + "build": "near-sdk-js build src/contract.ts build/hello_near.wasm", + "deploy": "near dev-deploy --wasmFile build/hello_near.wasm", + "test": "cd sandbox-ts && $npm_execpath run test -- -- ../build/hello_near.wasm", + "postinstall": "cd sandbox-ts && $npm_execpath i" + }, + "dependencies": { + "near-cli": "^3.4.2", + "near-sdk-js": "1.0.0" + }, + "devDependencies": { + "typescript": "^5.2.2", + "ts-morph": "^20.0.0" + } +} diff --git a/templates/contracts/js/src/contract.ts b/templates/contracts/ts/src/contract.ts similarity index 100% rename from templates/contracts/js/src/contract.ts rename to templates/contracts/ts/src/contract.ts diff --git a/templates/contracts/js/tsconfig.json b/templates/contracts/ts/tsconfig.json similarity index 100% rename from templates/contracts/js/tsconfig.json rename to templates/contracts/ts/tsconfig.json diff --git a/templates/frontend/gateway/.env b/templates/frontend/gateway/.env deleted file mode 100644 index bee86de09..000000000 --- a/templates/frontend/gateway/.env +++ /dev/null @@ -1 +0,0 @@ -CONTRACT_NAME=hello.near-examples.near \ No newline at end of file diff --git a/templates/frontend/gateway/.nvmrc b/templates/frontend/gateway/.nvmrc deleted file mode 100644 index 25bf17fc5..000000000 --- a/templates/frontend/gateway/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -18 \ No newline at end of file diff --git a/templates/frontend/gateway/.prettierrc b/templates/frontend/gateway/.prettierrc deleted file mode 100644 index 4c2dc1b1f..000000000 --- a/templates/frontend/gateway/.prettierrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "all", - "singleAttributePerLine": false, - "printWidth": 120 -} \ No newline at end of file diff --git a/templates/frontend/gateway/next.config.js b/templates/frontend/gateway/next.config.js deleted file mode 100644 index 71d991284..000000000 --- a/templates/frontend/gateway/next.config.js +++ /dev/null @@ -1,15 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - compiler: { styledComponents: true }, - reactStrictMode: true, - redirects: async () => { - return [ - ]; - }, - env: { - // adding CONTRACT_NAME as a public env variable - NEXT_PUBLIC_CONTRACT_NAME: process.env.CONTRACT_NAME, - } -}; - -module.exports = nextConfig; \ No newline at end of file diff --git a/templates/frontend/gateway/package.json b/templates/frontend/gateway/package.json deleted file mode 100644 index 1b4b9e076..000000000 --- a/templates/frontend/gateway/package.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "name": "near-gateway", - "version": "1.0.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next dev", - "prettier": "prettier --check 'src/**/*.{js,jsx,ts,tsx,json}'", - "prettier:write": "prettier --write 'src/**/*.{js,jsx,ts,tsx,json}'" - }, - "dependencies": { - "@braintree/sanitize-url": "6.0.0", - "@keypom/selector": "1.2.3", - "@monaco-editor/react": "^4.4.6", - "@near-js/biometric-ed25519": "0.3.0", - "@near-wallet-selector/core": "8.5.1", - "@near-wallet-selector/here-wallet": "8.5.1", - "@near-wallet-selector/meteor-wallet": "8.5.1", - "@near-wallet-selector/modal-ui": "8.5.1", - "@near-wallet-selector/my-near-wallet": "8.5.1", - "@near-wallet-selector/near-wallet": "8.5.1", - "@near-wallet-selector/neth": "8.5.1", - "@near-wallet-selector/nightly": "8.5.1", - "@near-wallet-selector/sender": "8.5.1", - "@near-wallet-selector/welldone-wallet": "8.5.1", - "@radix-ui/react-accordion": "^1.1.1", - "@radix-ui/react-dropdown-menu": "^2.0.4", - "@radix-ui/react-navigation-menu": "^1.1.2", - "@radix-ui/react-toast": "^1.1.3", - "@types/node": "18.16.3", - "@types/react": "18.2.0", - "@types/react-dom": "18.2.1", - "@web3-onboard/core": "^2.20.2", - "@web3-onboard/injected-wallets": "^2.10.1", - "@web3-onboard/ledger": "^2.4.6", - "@web3-onboard/react": "^2.8.7", - "@web3-onboard/walletconnect": "^2.3.9", - "big.js": "^6.1.1", - "bootstrap": "^5.2.1", - "bootstrap-icons": "^1.9.0", - "classnames": "^2.3.2", - "eslint": "8.39.0", - "eslint-config-next": "13.3.4", - "firebase": "^9.19.1", - "iframe-resizer-react": "^1.1.0", - "local-storage": "^2.0.0", - "lodash": "^4.17.21", - "near-fastauth-wallet": "^0.0.8", - "near-social-vm": "github:NearSocial/VM#2.5.1", - "next": "13.3.4", - "prettier": "^2.7.1", - "react": "18.2.0", - "react-bootstrap": "^2.5.0", - "react-bootstrap-typeahead": "^6.1.2", - "react-dom": "18.2.0", - "react-hook-form": "^7.43.9", - "react-singleton-hook": "^3.1.1", - "remark": "^14.0.3", - "rudder-sdk-js": "^2.36.0", - "strip-markdown": "^5.0.1", - "styled-components": "^5.3.6", - "typescript": "5.0.4", - "zustand": "^4.3.7" - }, - "devDependencies": { - "@types/big.js": "^6.1.6", - "@types/lodash": "^4.14.194", - "@types/styled-components": "^5.1.26", - "@typescript-eslint/eslint-plugin": "^5.59.2", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-simple-import-sort": "^10.0.0" - } -} diff --git a/templates/frontend/gateway/public/apple-touch-icon.png b/templates/frontend/gateway/public/apple-touch-icon.png deleted file mode 100644 index 2189f415d..000000000 Binary files a/templates/frontend/gateway/public/apple-touch-icon.png and /dev/null differ diff --git a/templates/frontend/gateway/public/bos-meta.png b/templates/frontend/gateway/public/bos-meta.png deleted file mode 100644 index ee84d09e0..000000000 Binary files a/templates/frontend/gateway/public/bos-meta.png and /dev/null differ diff --git a/templates/frontend/gateway/public/favicon.png b/templates/frontend/gateway/public/favicon.png deleted file mode 100644 index abfae7c1d..000000000 Binary files a/templates/frontend/gateway/public/favicon.png and /dev/null differ diff --git a/templates/frontend/gateway/public/fonts/FKGrotesk.woff2 b/templates/frontend/gateway/public/fonts/FKGrotesk.woff2 deleted file mode 100644 index ec97bcfc8..000000000 Binary files a/templates/frontend/gateway/public/fonts/FKGrotesk.woff2 and /dev/null differ diff --git a/templates/frontend/gateway/public/fonts/Mona-Sans.woff2 b/templates/frontend/gateway/public/fonts/Mona-Sans.woff2 deleted file mode 100644 index 8208a5000..000000000 Binary files a/templates/frontend/gateway/public/fonts/Mona-Sans.woff2 and /dev/null differ diff --git a/templates/frontend/gateway/public/logo192.png b/templates/frontend/gateway/public/logo192.png deleted file mode 100644 index 7e5fcc65f..000000000 Binary files a/templates/frontend/gateway/public/logo192.png and /dev/null differ diff --git a/templates/frontend/gateway/public/robots.txt b/templates/frontend/gateway/public/robots.txt deleted file mode 100644 index 24c3915dc..000000000 --- a/templates/frontend/gateway/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -User-agent: * -Sitemap: https://beta.near.org/sitemap.xml -Sitemap: https://near.org/sitemap.xml \ No newline at end of file diff --git a/templates/frontend/gateway/public/site.webmanifest b/templates/frontend/gateway/public/site.webmanifest deleted file mode 100644 index cbf188eac..000000000 --- a/templates/frontend/gateway/public/site.webmanifest +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "NEAR isn't just a Layer 1 blockchain — it's the platform for an Open Web.", - "short_name": "NEAR", - "icons": [], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/templates/frontend/gateway/src/assets/images/near-icon.svg b/templates/frontend/gateway/src/assets/images/near-icon.svg deleted file mode 100644 index ed33db6be..000000000 --- a/templates/frontend/gateway/src/assets/images/near-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/templates/frontend/gateway/src/components/MetaTags.tsx b/templates/frontend/gateway/src/components/MetaTags.tsx deleted file mode 100644 index a5c8bc5d3..000000000 --- a/templates/frontend/gateway/src/components/MetaTags.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import Head from 'next/head'; - -type Props = { - title: string; - description: string; - image?: string | null; -}; - -export function MetaTags(props: Props) { - return ( - - {props.title} - - - - - - - - - ); -} diff --git a/templates/frontend/gateway/src/components/component/ComponentWrapperPage.tsx b/templates/frontend/gateway/src/components/component/ComponentWrapperPage.tsx deleted file mode 100644 index 6103f4cf9..000000000 --- a/templates/frontend/gateway/src/components/component/ComponentWrapperPage.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { VmComponent } from '@/components/vm/VmComponent'; - -import { MetaTags } from '../MetaTags'; - -type Props = { - componentProps?: Record; - src: string; - meta?: { - title: string; - description: string; - }; -}; - -export function ComponentWrapperPage(props: Props) { - return ( - <> - {props.meta && } -
- -
- - ); -} diff --git a/templates/frontend/gateway/src/components/layouts/DefaultLayout.tsx b/templates/frontend/gateway/src/components/layouts/DefaultLayout.tsx deleted file mode 100644 index 70b4ea7fc..000000000 --- a/templates/frontend/gateway/src/components/layouts/DefaultLayout.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { ReactNode } from 'react'; - -import { Navigation } from '../navigation/Navigation'; - -interface Props { - children: ReactNode; -} - -export function DefaultLayout({ children }: Props) { - return ( - <> - - {children} - - ); -} diff --git a/templates/frontend/gateway/src/components/layouts/SimpleLayout.tsx b/templates/frontend/gateway/src/components/layouts/SimpleLayout.tsx deleted file mode 100644 index ba3f6056d..000000000 --- a/templates/frontend/gateway/src/components/layouts/SimpleLayout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { ReactNode } from 'react'; - -interface Props { - children: ReactNode; -} - -export function SimpleLayout({ children }: Props) { - return <>{children}; -} diff --git a/templates/frontend/gateway/src/components/lib/Button/Button.tsx b/templates/frontend/gateway/src/components/lib/Button/Button.tsx deleted file mode 100644 index 63b72a46f..000000000 --- a/templates/frontend/gateway/src/components/lib/Button/Button.tsx +++ /dev/null @@ -1,379 +0,0 @@ -import type { ButtonHTMLAttributes } from 'react'; -import { forwardRef } from 'react'; -import styled from 'styled-components'; - -type Fill = 'solid' | 'outline' | 'ghost'; -type Size = 'small' | 'default' | 'large'; -type Variant = 'primary' | 'secondary' | 'affirmative' | 'destructive'; - -type Props = Omit, 'size'> & { - disabled?: boolean; - fill?: Fill; - href?: string; - icon?: string; - iconLeft?: string; - iconRight?: string; - label: string; - loading?: boolean; - size?: Size; - type?: 'button' | 'submit'; - variant?: Variant; -}; - -type StyledProps = { - disabled?: boolean; - fill: Fill; - icon?: string; - loading?: boolean; - size: Size; - variant: Variant; -}; - -const variants: Record = { - primary: { - outline: { - background: 'var(--sand1)', - border: 'var(--sand6)', - color: 'var(--violet8)', - iconColor: 'var(--violet9)', - hover: { - border: 'var(--violet6)', - }, - focus: { - border: 'var(--violet9)', - }, - active: { - background: 'var(--violet2)', - border: 'var(--violet7)', - }, - }, - solid: { - background: 'var(--sand12)', - border: 'var(--sand12)', - color: 'var(--sand1)', - iconColor: 'var(--sand9)', - hover: { - background: 'var(--sand11)', - border: 'var(--sand11)', - }, - focus: {}, - active: {}, - }, - }, - secondary: { - outline: { - background: 'var(--sand1)', - border: 'var(--sand6)', - color: 'var(--sand12)', - iconColor: 'var(--sand10)', - hover: { - border: 'var(--sand8)', - }, - focus: { - border: 'var(--violet8)', - }, - active: { - background: 'var(--sand3)', - border: 'var(--sand8)', - }, - }, - solid: { - background: 'var(--sand3)', - border: 'var(--sand6)', - color: 'var(--sand12)', - iconColor: 'var(--sand11)', - hover: { - background: 'var(--sand4)', - }, - focus: { - border: 'var(--violet8)', - }, - active: { - background: 'var(--sand5)', - }, - }, - }, - destructive: { - outline: { - background: 'var(--sand1)', - border: 'var(--sand6)', - color: 'var(--red8)', - iconColor: 'var(--red9)', - hover: { - border: 'var(--red6)', - }, - focus: { - border: 'var(--violet8)', - }, - active: { - background: 'var(--red2)', - border: 'var(--red7)', - }, - }, - solid: { - background: 'var(--red9)', - border: 'var(--red8)', - color: 'var(--red12)', - iconColor: 'var(--red11)', - hover: { - background: 'var(--red10)', - }, - focus: { - border: 'var(--red11)', - }, - active: { - background: 'var(--red8)', - }, - }, - }, - affirmative: { - outline: { - background: 'var(--sand1)', - border: 'var(--sand6)', - color: 'var(--green11)', - iconColor: 'var(--green10)', - hover: { - border: 'var(--green9)', - }, - focus: { - border: 'var(--violet8)', - }, - active: { - background: 'var(--green2)', - border: 'var(--green8)', - }, - }, - solid: { - background: 'var(--green9)', - border: 'var(--green8)', - color: 'var(--green12)', - iconColor: 'var(--green11)', - hover: { - background: 'var(--green10)', - }, - focus: { - border: 'var(--green11)', - }, - active: { - background: 'var(--green8)', - }, - }, - }, -}; -variants.primary.ghost = { - ...variants.primary.outline, - border: 'hsla(0, 0%, 100%, 0)', - background: 'hsla(0, 0%, 100%, 0)', -}; -variants.secondary.ghost = { - ...variants.secondary.outline, - border: 'hsla(0, 0%, 100%, 0)', - background: 'hsla(0, 0%, 100%, 0)', -}; -variants.destructive.ghost = { - ...variants.destructive.outline, - border: 'hsla(0, 0%, 100%, 0)', - background: 'hsla(0, 0%, 100%, 0)', -}; -variants.affirmative.ghost = { - ...variants.affirmative.outline, - border: 'hsla(0, 0%, 100%, 0)', - background: 'hsla(0, 0%, 100%, 0)', -}; - -const sizes: Record = { - small: { - font: 'var(--text-xs)', - gap: '6px', - height: '32px', - icon: '14px', - paddingX: '16px', - }, - default: { - font: 'var(--text-s)', - gap: '8px', - height: '40px', - icon: '18px', - paddingX: '20px', - }, - large: { - font: 'var(--text-base)', - gap: '8px', - height: '48px', - icon: '18px', - paddingX: '24px', - }, -}; - -function returnColor(variant: Variant, fill: string, state: string, key: string) { - if (state === 'default') return variants[variant][fill][key]; - return variants[variant][fill][state][key] || variants[variant][fill][key]; -} - -const StyledButton = styled.button` - all: unset; - box-sizing: border-box; - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - width: ${(p) => (p.icon ? sizes[p.size].height : undefined)}; - height: ${(p) => sizes[p.size].height}; - padding: ${(p) => (p.icon ? '0' : `0 ${sizes[p.size].paddingX}`)}; - font: ${(p) => sizes[p.size].font}; - font-weight: 600; - line-height: 1; - border-radius: 100px; - background: ${(p) => returnColor(p.variant, p.fill, 'default', 'background')}; - color: ${(p) => returnColor(p.variant, p.fill, 'default', 'color')}; - border: 1px solid ${(p) => returnColor(p.variant, p.fill, 'default', 'border')}; - box-shadow: 0 0 0 0px var(--violet4); - cursor: pointer; - transition: all 200ms; - text-decoration: none !important; - - &:hover { - background: ${(p) => returnColor(p.variant, p.fill, 'hover', 'background')}; - color: ${(p) => returnColor(p.variant, p.fill, 'hover', 'color')}; - border: 1px solid ${(p) => returnColor(p.variant, p.fill, 'hover', 'border')}; - } - &:focus { - background: ${(p) => returnColor(p.variant, p.fill, 'focus', 'background')}; - color: ${(p) => returnColor(p.variant, p.fill, 'focus', 'color')}; - border: 1px solid ${(p) => returnColor(p.variant, p.fill, 'focus', 'border')}; - box-shadow: 0 0 0 4px var(--violet4); - } - &:active { - background: ${(p) => returnColor(p.variant, p.fill, 'active', 'background')}; - color: ${(p) => returnColor(p.variant, p.fill, 'active', 'color')}; - border: 1px solid ${(p) => returnColor(p.variant, p.fill, 'active', 'border')}; - } - - ${(p) => - p.loading && - ` - pointer-events: none; - `} - - ${(p) => - p.disabled && - ` - opacity: 1; - background: ${p.fill === 'ghost' ? 'hsla(0, 0%, 100%, 0)' : 'var(--sand3)'}; - border-color: var(--sand3); - color: var(--sand8); - pointer-events: none; - - i { - color: var(--sand8) !important; - } - `} -`; - -const Inner = styled.span` - display: inline-flex; - align-items: center; - justify-content: center; - gap: ${(p) => sizes[p.size].gap}; - - i { - font-size: ${(p) => sizes[p.size].icon}; - line-height: ${(p) => sizes[p.size].icon}; - color: ${(p) => (p.icon ? undefined : returnColor(p.variant, p.fill, 'default', 'iconColor'))}; - } - - ${(p) => - p.loading && - ` - opacity: 0; - `} -`; - -const Label = styled.span``; - -const Spinner = styled.i` - position: absolute; - top: 50%; - left: 0; - right: 0; - margin: calc(${(p) => sizes[p.size].icon} * -0.5) auto 0; - width: ${(p) => sizes[p.size].icon}; - height: ${(p) => sizes[p.size].icon}; - font-size: ${(p) => sizes[p.size].icon}; - line-height: ${(p) => sizes[p.size].icon}; - animation: spin 800ms infinite linear; - - @keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } - } -`; - -export const Button = forwardRef( - ( - { - disabled, - fill = 'solid', - href, - icon, - iconLeft, - iconRight, - label, - loading, - size = 'default', - type = 'button', - variant = 'primary', - ...forwardedProps - }, - ref, - ) => { - const conditionalAttributes: Record = href - ? { - as: 'a', - href, - } - : { - type, - disabled: disabled || loading, - }; - - if (icon) { - conditionalAttributes['aria-label'] = label; - } - - const styledProps: StyledProps = { - disabled, - fill, - icon, - loading, - size, - variant, - }; - - return ( - - <> - {loading && } - - {icon ? ( - - ) : ( - <> - {iconLeft && } - - {iconRight && } - - )} - - - - ); - }, -); - -Button.displayName = 'Button'; diff --git a/templates/frontend/gateway/src/components/lib/Button/index.tsx b/templates/frontend/gateway/src/components/lib/Button/index.tsx deleted file mode 100644 index 8b166a86e..000000000 --- a/templates/frontend/gateway/src/components/lib/Button/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './Button'; diff --git a/templates/frontend/gateway/src/components/lib/Spinner/Spinner.tsx b/templates/frontend/gateway/src/components/lib/Spinner/Spinner.tsx deleted file mode 100644 index 6f9ef921e..000000000 --- a/templates/frontend/gateway/src/components/lib/Spinner/Spinner.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import styled from 'styled-components'; - -const Wrapper = styled.span` - display: inline-flex; - width: 100%; - align-items: center; - justify-content: center; - padding: 12px; - animation: spin 1200ms infinite linear; - - i { - color: currentColor; - font-size: 16px; - line-height: 16px; - } - - @keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } - } -`; - -export function Spinner() { - return ( - - - - ); -} diff --git a/templates/frontend/gateway/src/components/lib/Spinner/index.ts b/templates/frontend/gateway/src/components/lib/Spinner/index.ts deleted file mode 100644 index b259397be..000000000 --- a/templates/frontend/gateway/src/components/lib/Spinner/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Spinner'; diff --git a/templates/frontend/gateway/src/components/lib/Text/Text.tsx b/templates/frontend/gateway/src/components/lib/Text/Text.tsx deleted file mode 100644 index af8921f73..000000000 --- a/templates/frontend/gateway/src/components/lib/Text/Text.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import styled from 'styled-components'; - -type Props = { - color?: string; - font?: string; - weight?: string; -}; - -export const Text = styled.p` - font: ${(p) => (p.font ? `var(--${p.font})` : 'var(--text-base)')}; - font-weight: ${(p) => p.weight ?? '400'}; - color: ${(p) => (p.color ? `var(--${p.color})` : 'currentColor')}; - margin: 0; -`; diff --git a/templates/frontend/gateway/src/components/lib/Text/index.tsx b/templates/frontend/gateway/src/components/lib/Text/index.tsx deleted file mode 100644 index b0c76af0b..000000000 --- a/templates/frontend/gateway/src/components/lib/Text/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './Text'; diff --git a/templates/frontend/gateway/src/components/lib/Toast/README.md b/templates/frontend/gateway/src/components/lib/Toast/README.md deleted file mode 100644 index d8dabeb73..000000000 --- a/templates/frontend/gateway/src/components/lib/Toast/README.md +++ /dev/null @@ -1,83 +0,0 @@ -# Toast - -Implemented via Radix primitives: https://www.radix-ui.com/docs/primitives/components/toast - -_If the current props and Stitches style overrides aren't enough to cover your use case, feel free to implement your own component using the Radix primitives directly._ - -## Example - -Using the `openToast` API allows you to easily open a toast from any context: - -```tsx -import { openToast } from '@/components/lib/Toast'; - -... - - -``` - -You can pass other options too: - -```tsx - -``` - -## Deduplicate - -If you need to ensure only a single instance of a toast is ever displayed at once, you can deduplicate by passing a unique `id` key. If a toast with the passed `id` is currently open, a new toast will not be opened: - -```tsx - -``` - -## Custom Toast - -If you need something more custom, you can render a custom toast using `lib/Toast/Toaster.tsx` as an example like so: - -```tsx -import * as Toast from '@/components/lib/Toast'; - -... - - - - My Title - My Description - - - - - -``` diff --git a/templates/frontend/gateway/src/components/lib/Toast/Toast.tsx b/templates/frontend/gateway/src/components/lib/Toast/Toast.tsx deleted file mode 100644 index a65a25cb6..000000000 --- a/templates/frontend/gateway/src/components/lib/Toast/Toast.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as ToastPrimitive from '@radix-ui/react-toast'; -import type { ComponentProps } from 'react'; -import { forwardRef } from 'react'; - -import * as S from './styles'; - -type CloseButtonProps = ComponentProps; - -export const Root = S.Root; -export const Title = S.Title; -export const Description = S.Description; -export const Content = S.Content; -export const Viewport = S.Viewport; -export const Action = ToastPrimitive.Action; -export const Provider = ToastPrimitive.Provider; -export const Close = ToastPrimitive.Close; - -export const CloseButton = forwardRef((props, ref) => { - return ( - - - - ); -}); -CloseButton.displayName = 'CloseButton'; diff --git a/templates/frontend/gateway/src/components/lib/Toast/Toaster.tsx b/templates/frontend/gateway/src/components/lib/Toast/Toaster.tsx deleted file mode 100644 index c3365a1d5..000000000 --- a/templates/frontend/gateway/src/components/lib/Toast/Toaster.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { Toast, ToastType } from './store'; -import { useToasterStore } from './store'; -import * as T from './Toast'; - -export function Toaster() { - const toaster = useToasterStore(); - - const iconsByType: Record = { - INFO: 'ph ph-info', - WARNING: 'ph ph-warning', - ERROR: 'ph ph-warning-circle', - SUCCESS: 'ph ph-check-circle', - }; - - function onOpenChange(open: boolean, toast: Toast) { - if (!open) toaster.close(toast); - } - - return ( - - {toaster.toasts.map((toast) => { - const type = toast.type || 'INFO'; - const icon = toast.icon || iconsByType[type]; - - return ( - onOpenChange(open, toast)} - key={toast.id} - > - - - - {toast.title} - {toast.description && {toast.description}} - - - - - ); - })} - - - - ); -} diff --git a/templates/frontend/gateway/src/components/lib/Toast/api.ts b/templates/frontend/gateway/src/components/lib/Toast/api.ts deleted file mode 100644 index 334dcbe8c..000000000 --- a/templates/frontend/gateway/src/components/lib/Toast/api.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { OpenToastOptions } from './store'; -import { useToasterStore } from './store'; - -export function openToast(options: OpenToastOptions) { - useToasterStore.getState().open(options); -} diff --git a/templates/frontend/gateway/src/components/lib/Toast/index.ts b/templates/frontend/gateway/src/components/lib/Toast/index.ts deleted file mode 100644 index 17c87006d..000000000 --- a/templates/frontend/gateway/src/components/lib/Toast/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './api'; -export * from './Toast'; -export * from './Toaster'; diff --git a/templates/frontend/gateway/src/components/lib/Toast/store.ts b/templates/frontend/gateway/src/components/lib/Toast/store.ts deleted file mode 100644 index d52a77ea9..000000000 --- a/templates/frontend/gateway/src/components/lib/Toast/store.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { create } from 'zustand'; - -export type ToastType = 'INFO' | 'ERROR' | 'SUCCESS' | 'WARNING'; - -export interface Toast { - description?: string; - duration?: number; - icon?: string; - id: string; - isOpen: boolean; - title: string; - type?: ToastType; -} - -export interface OpenToastOptions { - description?: string; - duration?: number; - icon?: string; - id?: string; - title: string; - type?: ToastType; -} - -interface ToasterStore { - toasts: Toast[]; - close: (toast: Toast) => void; - destroy: (toast: Toast) => void; - open: (options: OpenToastOptions) => void; -} - -export const useToasterStore = create((set) => ({ - toasts: [], - - close: (toast) => { - set((state) => { - const toasts = state.toasts.map((t) => { - if (t.id === toast.id) { - return { - ...t, - isOpen: false, - }; - } - - return t; - }); - - setTimeout(() => { - state.destroy(toast); - }, 5000); - - return { - toasts, - }; - }); - }, - - destroy: (toast) => { - set((state) => { - const toasts = state.toasts.filter((t) => t.id !== toast.id); - - return { - toasts, - }; - }); - }, - - open: (options) => { - const newToast = { - ...options, - isOpen: true, - id: options.id || Date.now().toString(), - type: options.type || 'INFO', - }; - - set((state) => { - const toasts = state.toasts.filter((t) => t.id !== newToast.id); - - return { - toasts: [...toasts, newToast], - }; - }); - }, -})); diff --git a/templates/frontend/gateway/src/components/lib/Toast/styles.ts b/templates/frontend/gateway/src/components/lib/Toast/styles.ts deleted file mode 100644 index db957642d..000000000 --- a/templates/frontend/gateway/src/components/lib/Toast/styles.ts +++ /dev/null @@ -1,126 +0,0 @@ -import * as ToastPrimitive from '@radix-ui/react-toast'; -import styled, { keyframes } from 'styled-components'; - -const hideAnimation = keyframes` - from { opacity: 1; } - to { opacity: 0; } -`; - -const slideInAnimation = keyframes` - from { transform: translateX(calc(100% + 1rem)) } - to { transform: translateX(0) } -`; - -const swipeOutAnimation = keyframes` - from { transform: translateX(var(--radix-toast-swipe-end-x)) } - to { transform: translateX(calc(100% + 1rem)) } -`; - -export const Viewport = styled(ToastPrimitive.Viewport)` - position: fixed; - bottom: 0; - right: 0; - display: flex; - flex-direction: column; - gap: 1rem; - padding: 1rem; - width: 100%; - max-height: 100vh; - max-width: 20rem; - z-index: 2147483632; -`; - -export const Root = styled(ToastPrimitive.Root)` - display: flex; - gap: 1rem; - align-items: center; - background: #000; - color: #fff; - border-radius: 1rem; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); - padding: 1rem; - - i { - font-size: 1.4rem; - } - - &[data-state='open'] { - animation: ${slideInAnimation} 200ms cubic-bezier(0.16, 1, 0.3, 1); - } - &[data-state='closed'] { - animation: ${hideAnimation} 200ms ease-in forwards; - } - &[data-swipe='move'] { - transform: translateX(var(--radix-toast-swipe-move-x)); - } - &[data-swipe='cancel'] { - transform: translateX(0); - transition: transform 200ms ease-out; - } - &[data-swipe='end'] { - animation: ${swipeOutAnimation} 100ms ease-out forwards; - } - - &[data-type='ERROR'] { - background: var(--red11); - } - &[data-type='SUCCESS'] { - background: var(--green11); - } - &[data-type='INFO'] { - background: var(--sand12); - } - &[data-type='WARNING'] { - background: var(--amber4); - color: var(--amber12); - } -`; - -export const Content = styled.div` - display: flex; - flex-direction: column; - width: 100%; - gap: 0rem; -`; - -export const Title = styled(ToastPrimitive.Title)` - font: 450 16px/1.5 'Mona Sans', sans-serif; - color: currentColor; -`; - -export const Description = styled(ToastPrimitive.Description)` - font: 450 14px/1.5 'Mona Sans', sans-serif; - color: currentColor; - opacity: 0.8; -`; - -export const CloseButton = styled(ToastPrimitive.Close)` - display: flex; - align-items: center; - justify-content: center; - width: 1.5rem; - height: 1.5rem; - flex-shrink: 0; - border-radius: 100%; - cursor: pointer; - color: #fff; - border: 2px solid rgba(255, 255, 255, 0.25); - background: none; - transition: all 200ms; - - &:hover { - border-color: rgba(255, 255, 255, 0.5); - } - - &:focus { - border-color: rgba(255, 255, 255, 1); - } - - i { - font-size: 0.8rem; - } - - &[data-type='WARNING'] { - color: var(--amber12); - } -`; diff --git a/templates/frontend/gateway/src/components/navigation/Navigation.tsx b/templates/frontend/gateway/src/components/navigation/Navigation.tsx deleted file mode 100644 index c52546b4e..000000000 --- a/templates/frontend/gateway/src/components/navigation/Navigation.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -import { DesktopNavigation } from './desktop/DesktopNavigation'; -import { MobileNavigation } from './mobile/MobileNavigation'; - -export const Navigation = () => { - const [matches, setMatches] = useState(true); - - useEffect(() => { - setMatches(window.matchMedia('(min-width: 1024px)').matches); - }, []); - - useEffect(() => { - window.matchMedia('(min-width: 1024px)').addEventListener('change', (e) => setMatches(e.matches)); - }, []); - - return ( - <> - {matches && } - {!matches && } - - ); -}; diff --git a/templates/frontend/gateway/src/components/navigation/UserDropdownMenu.tsx b/templates/frontend/gateway/src/components/navigation/UserDropdownMenu.tsx deleted file mode 100644 index e202971b0..000000000 --- a/templates/frontend/gateway/src/components/navigation/UserDropdownMenu.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; -import styled from 'styled-components'; - -import { VmComponent } from '@/components/vm/VmComponent'; -import { useBosComponents } from '@/hooks/useBosComponents'; -import { useAuthStore } from '@/stores/auth'; - -const Wrapper = styled.div` - > button { - all: unset; - display: flex; - align-items: center; - border-radius: 50px; - background-color: var(--sand12); - padding: 4px; - transition: all 200ms; - - &:hover { - background-color: var(--black); - } - - &:focus { - box-shadow: 0 0 0 4px var(--violet4); - } - } - .d-inline-block { - width: unset !important; - height: unset !important; - img { - border-radius: 50% !important; - width: 38px !important; - height: 38px !important; - } - } - - i { - color: #a1a09a; - margin: 0 5px 0 0; - } - - .profile-info { - margin: 0 8px; - line-height: normal; - max-width: 110px; - font-size: 12px; - - .profile-name, - .profile-username { - text-overflow: ellipsis; - overflow: hidden; - } - - .profile-name { - color: white; - } - .profile-username { - color: #a1a09a; - } - } - - .DropdownMenuContent { - background-color: #161615; - border-radius: 6px; - margin-top: 11px; - padding: 12px; - box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); - animation-duration: 600ms; - animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); - will-change: transform, opacity; - z-index: 10000000; - } - .DropdownMenuContent[data-side='top'] { - animation-name: slideDownAndFade; - } - .DropdownMenuContent[data-side='right'] { - animation-name: slideLeftAndFade; - } - .DropdownMenuContent[data-side='bottom'] { - animation-name: slideUpAndFade; - } - .DropdownMenuContent[data-side='left'] { - animation-name: slideRightAndFade; - } - - .DropdownMenuItem { - all: unset; - font-size: 13px; - line-height: 1; - color: #9ba1a6; - border-radius: 3px; - display: flex; - align-items: center; - padding: 12px; - position: relative; - user-select: none; - outline: none; - } - - .DropdownMenuItem:hover { - color: white; - cursor: pointer; - } - - .DropdownMenuItem i { - font-size: 20px; - margin-right: 10px; - } - - @keyframes slideUpAndFade { - from { - opacity: 0; - transform: translateY(2px); - } - to { - opacity: 1; - transform: translateY(0); - } - } - - @keyframes slideRightAndFade { - from { - opacity: 0; - transform: translateX(-2px); - } - to { - opacity: 1; - transform: translateX(0); - } - } - - @keyframes slideDownAndFade { - from { - opacity: 0; - transform: translateY(-2px); - } - to { - opacity: 1; - transform: translateY(0); - } - } - - @keyframes slideLeftAndFade { - from { - opacity: 0; - transform: translateX(2px); - } - to { - opacity: 1; - transform: translateX(0); - } - } - - @media (max-width: 800px) { - .profile-info, - .ph { - display: none; - } - - > button { - background: var(--sand6); - padding: 1px; - } - - .d-inline-block { - img { - width: 43px !important; - height: 43px !important; - } - } - } -`; - -export const UserDropdownMenu = () => { - const accountId = useAuthStore((store) => store.accountId); - const logOut = useAuthStore((store) => store.logOut); - const components = useBosComponents(); - - return ( - - - - -
-
- -
-
{accountId}
-
- -
- - - logOut()}> - - Sign out - - - - -
-
- ); -}; diff --git a/templates/frontend/gateway/src/components/navigation/desktop/DesktopNavigation.tsx b/templates/frontend/gateway/src/components/navigation/desktop/DesktopNavigation.tsx deleted file mode 100644 index b4215f71f..000000000 --- a/templates/frontend/gateway/src/components/navigation/desktop/DesktopNavigation.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import Image from 'next/image'; -import Link from 'next/link'; -import { useEffect, useState } from 'react'; -import styled from 'styled-components'; - -import { Button } from '@/components/lib/Button'; -import { useAuthStore } from '@/stores/auth'; - -import NearLogo from '../icons/near-icon.svg'; -import { UserDropdownMenu } from '../UserDropdownMenu'; -import { MainNavigationMenu } from './MainNavigationMenu'; - -const Wrapper = styled.div<{ - scrolled?: boolean; -}>` - --nav-height: 75px; - z-index: 1000; - position: sticky; - top: 0; - left: 0; - right: 0; - background-color: white; - height: var(--nav-height); - box-shadow: 0 1px 0 var(--sand6); -`; - -const Container = styled.div` - display: flex; - align-items: center; - height: 100%; - margin: 0 auto; -`; - -const Logo = styled.a` - text-decoration: none; - cursor: pointer; - outline: none; - transition: all 200ms; - border-radius: 4px; - - &:focus { - outline: 2px solid var(--violet4); - outline-offset: 0.3rem; - } - - img { - width: 110px; - } -`; - -const Actions = styled.div` - display: flex; - align-items: center; - margin-left: auto; - position: relative; - z-index: 10; - gap: 0.5rem; -`; - -export const DesktopNavigation = () => { - const [scrolled, setScrolled] = useState(false); - const signedIn = useAuthStore((store) => store.signedIn); - const requestSignInWithWallet = useAuthStore((store) => store.requestSignInWithWallet); - - useEffect(() => { - const handleScroll = () => { - if (window.scrollY > 0) { - setScrolled(true); - } else { - setScrolled(false); - } - }; - - window.addEventListener('scroll', handleScroll); - - return () => { - window.removeEventListener('scroll', handleScroll); - }; - }, []); - - return ( - <> - - - - - - NEAR - - - - - - - {signedIn ? ( - <> - - - ) : ( - <> - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/frontend/vanilla/src/components.js b/templates/frontend/vanilla/src/components.js new file mode 100644 index 000000000..0cb2f3094 --- /dev/null +++ b/templates/frontend/vanilla/src/components.js @@ -0,0 +1,63 @@ +import { createRoot } from 'react-dom/client'; +import { Wallet } from './near-wallet'; +import { useInitNear, Widget } from 'near-social-vm'; +import { useEffect } from 'react'; + +const CONTRACT_ADDRESS = 'v1.social08.testnet'; + +// When creating the wallet you can optionally ask to create an access key +// Having the key enables to call non-payable methods without interrupting the user to sign +const wallet = new Wallet({createAccessKeyFor: CONTRACT_ADDRESS, network: 'testnet'}); + +export default function Component({ src }) { + + const { initNear } = useInitNear(); + + useEffect(() => { + initNear && initNear({ networkId: wallet.network, selector: wallet.selector }); + }, [initNear]); + + return ( +
+ +

Source: {src}

+
+ ); +} + +window.onload = async () => { + let isSignedIn = await wallet.startUp(); + isSignedIn? signedInUI(): signedOutUI(); + + const domNode = document.getElementById('components'); + const root = createRoot(domNode); + root.render( +
+
+ +

 

+ +
+
+ +
+
+ ); +}; + +// Button clicks +document.querySelector('#sign-in-button').onclick = () => { wallet.signIn() }; +document.querySelector('#sign-out-button').onclick = () => { wallet.signOut() }; + +// UI: Display the signed-out container +function signedOutUI() { + document.querySelectorAll('#sign-out-button').forEach(el => el.style.display = 'none'); +} + +// UI: Displaying the signed in flow container and fill in account-specific data +function signedInUI() { + document.querySelectorAll('#sign-in-button').forEach(el => el.style.display = 'none'); + document.querySelectorAll('[data-behavior=account-id]').forEach(el => { + el.innerText = wallet.accountId; + }); +} \ No newline at end of file diff --git a/templates/frontend/vanilla/src/hello-near.html b/templates/frontend/vanilla/src/hello-near.html new file mode 100644 index 000000000..44916bf19 --- /dev/null +++ b/templates/frontend/vanilla/src/hello-near.html @@ -0,0 +1,80 @@ + + + + + + + + + + + Welcome to NEAR + + + + + +
+ + + +
+
+

The contract says:

+
+
+
+ Please login to change the greeting +
+
+
+
+ + +
+
+
+
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/templates/frontend/vanilla/src/hello.js b/templates/frontend/vanilla/src/hello.js new file mode 100644 index 000000000..3fd61e8f4 --- /dev/null +++ b/templates/frontend/vanilla/src/hello.js @@ -0,0 +1,61 @@ +import { Wallet } from './near-wallet'; + +const CONTRACT_ADDRESS = 'v1.social08.testnet'; + +// When creating the wallet you can optionally ask to create an access key +// Having the key enables to call non-payable methods without interrupting the user to sign +const wallet = new Wallet({createAccessKeyFor: CONTRACT_ADDRESS, network: 'testnet'}); + +// Setup on page load +window.onload = async () => { + let isSignedIn = await wallet.startUp(); + isSignedIn? signedInUI(): signedOutUI(); + getGreeting(); +}; + +// Button clicks +document.querySelector('form').onsubmit = setGreeting; +document.querySelector('#sign-in-button').onclick = () => { wallet.signIn() }; +document.querySelector('#sign-out-button').onclick = () => { wallet.signOut() }; + +async function setGreeting(event) { + event.preventDefault(); + + // handle UI + document.querySelector('#signed-in').classList.add('please-wait'); + + // use the wallet to send the greeting to the Smart Contract + const { greeting } = event.target.elements; + await wallet.callMethod({ method: 'set_greeting', args: { greeting: greeting.value }, contractId: CONTRACT_ADDRESS }); + + // query the new greeting + await getGreeting(); + + // handle UI + document.querySelector('#signed-in').classList.remove('please-wait'); +} + +async function getGreeting() { + // use the wallet to query the Smart Contract + const currentGreeting = await wallet.viewMethod({ method: 'get_greeting', contractId: CONTRACT_ADDRESS }); + + // Display it + document.querySelector('#displayGreeting').innerText = currentGreeting; +} + +// UI: Hide signed-in elements +function signedOutUI() { hide('#signed-in'); hide('#sign-out-button'); } + +// UI: Hide signed-out elements +function signedInUI() { + hide('#signed-out'); + hide('#sign-in-button'); + + document.querySelectorAll('[data-behavior=account-id]').forEach(el => { + el.innerText = wallet.accountId; + }); +} + +function hide(id){ + document.querySelectorAll(id).forEach(el => el.style.display = 'none'); +} \ No newline at end of file diff --git a/templates/frontend/vanilla/src/index.html b/templates/frontend/vanilla/src/index.html new file mode 100644 index 000000000..7f9888c9d --- /dev/null +++ b/templates/frontend/vanilla/src/index.html @@ -0,0 +1,65 @@ + + + + + + + + + + + Welcome to NEAR + + + + + + + + + + \ No newline at end of file diff --git a/templates/frontend/vanilla/near-wallet.js b/templates/frontend/vanilla/src/near-wallet.js similarity index 78% rename from templates/frontend/vanilla/near-wallet.js rename to templates/frontend/vanilla/src/near-wallet.js index d9625f24d..75de86808 100644 --- a/templates/frontend/vanilla/near-wallet.js +++ b/templates/frontend/vanilla/src/near-wallet.js @@ -14,54 +14,55 @@ const NO_DEPOSIT = '0'; // Wallet that simplifies using the wallet selector export class Wallet { - walletSelector; + selector; wallet; network; createAccessKeyFor; - constructor({ createAccessKeyFor = undefined, network = 'testnet' }) { + constructor({ createAccessKeyFor = undefined, network = 'mainnet' }) { // Login to a wallet passing a contractId will create a local // key, so the user skips signing non-payable transactions. // Omitting the accountId will result in the user being // asked to sign all transactions. - this.createAccessKeyFor = createAccessKeyFor; - this.network = 'testnet'; - } - - // To be called when the website loads - async startUp() { - this.walletSelector = await setupWalletSelector({ + this.createAccessKeyFor = createAccessKeyFor + this.network = network + this.selector = setupWalletSelector({ network: this.network, modules: [setupMyNearWallet()], }); + } - const isSignedIn = this.walletSelector.isSignedIn(); + // To be called when the website loads + async startUp() { + const walletSelector = await this.selector; + const isSignedIn = walletSelector.isSignedIn(); if (isSignedIn) { - this.wallet = await this.walletSelector.wallet(); - this.accountId = this.walletSelector.store.getState().accounts[0].accountId; + this.wallet = await walletSelector.wallet(); + this.accountId = walletSelector.store.getState().accounts[0].accountId; } return isSignedIn; } // Sign-in method - signIn() { + async signIn() { const description = 'Please select a wallet to sign in.'; - const modal = setupModal(this.walletSelector, { contractId: this.createAccessKeyFor, description }); + const modal = setupModal(await this.selector, { contractId: this.createAccessKeyFor, description }); modal.show(); } // Sign-out method - signOut() { - this.wallet.signOut(); + async signOut() { + await this.wallet.signOut(); this.wallet = this.accountId = this.createAccessKeyFor = null; window.location.replace(window.location.origin + window.location.pathname); } // Make a read-only call to retrieve information from the network async viewMethod({ contractId, method, args = {} }) { - const { network } = this.walletSelector.options; + const walletSelector = await this.selector; + const { network } = walletSelector.options; const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); let res = await provider.query({ @@ -96,7 +97,8 @@ export class Wallet { // Get transaction result from the network async getTransactionResult(txhash) { - const { network } = this.walletSelector.options; + const walletSelector = await this.selector; + const { network } = walletSelector.options; const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); // Retrieve transaction result from the network diff --git a/templates/frontend/vanilla/webpack.config.js b/templates/frontend/vanilla/webpack.config.js new file mode 100644 index 000000000..a37130a80 --- /dev/null +++ b/templates/frontend/vanilla/webpack.config.js @@ -0,0 +1,79 @@ +const path = require('path'); +const webpack = require('webpack') + +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +module.exports = { + mode: 'development', + entry: { + hello: './src/hello.js', + components: './src/components.js', + 'near-wallet': './src/near-wallet.js', + }, + devtool: 'inline-source-map', + devServer: { + static: './dist', + }, + output: { + filename: '[name].js', + path: path.resolve(__dirname, 'dist'), + clean: true, + }, + resolve: { + fallback: { + https: require.resolve("https-browserify"), + http: require.resolve("stream-http"), + crypto: require.resolve("crypto-browserify"), + } + }, + module: { + rules: [ + { + test: /\.(js)$/, + exclude: /node_modules/, + use: ['babel-loader'] + }, + { + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, + { + test: /\.(png|svg|jpg|jpeg|gif)$/i, + type: 'asset/resource', + }, + { + test: /\.m?js/, + resolve: { + fullySpecified: false + } + } + ], + }, + plugins: [ + new CopyWebpackPlugin({ + patterns: [{ from: 'src/assets', to: 'assets' }] + }), + new HtmlWebpackPlugin({ + filename: 'index.html', + template: './src/index.html', + chunks: ['index'], + }), + new HtmlWebpackPlugin({ + filename: 'hello-near.html', + template: './src/hello-near.html', + chunks: ['index'], + }), + new HtmlWebpackPlugin({ + filename: 'components.html', + template: './src/components.html', + chunks: ['components'], + }), + new webpack.ProvidePlugin({ + process: 'process/browser', + }), + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'], + }), + ] +}; \ No newline at end of file diff --git a/templates/integration-tests/rust-tests/Cargo.toml b/templates/sandbox-tests/sandbox-rs/Cargo.toml similarity index 87% rename from templates/integration-tests/rust-tests/Cargo.toml rename to templates/sandbox-tests/sandbox-rs/Cargo.toml index bc36ff435..8d54a2946 100644 --- a/templates/integration-tests/rust-tests/Cargo.toml +++ b/templates/sandbox-tests/sandbox-rs/Cargo.toml @@ -1,22 +1,22 @@ [package] -name = "integration-tests" +name = "sandbox" version = "1.0.0" publish = false -edition = "2018" +edition = "2021" [dev-dependencies] anyhow = "1.0" borsh = "0.9" maplit = "1.0" near-units = "0.2.0" -# arbitrary_precision enabled for u128 types that workspaces requires for Balance types -serde_json = { version = "1.0", features = ["arbitrary_precision"] } tokio = { version = "1.18.1", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3.11", features = ["env-filter"] } workspaces = "0.6.0" pkg-config = "0.3.1" +serde_json = { version = "1.0", features = ["arbitrary_precision"] } +# arbitrary_precision enabled for u128 types that workspaces requires for Balance types [[example]] -name = "integration-tests" +name = "sandbox" path = "src/tests.rs" diff --git a/templates/integration-tests/rust-tests/src/tests.rs b/templates/sandbox-tests/sandbox-rs/src/tests.rs similarity index 100% rename from templates/integration-tests/rust-tests/src/tests.rs rename to templates/sandbox-tests/sandbox-rs/src/tests.rs diff --git a/templates/integration-tests/js-tests/ava.config.cjs b/templates/sandbox-tests/sandbox-ts/ava.config.cjs similarity index 100% rename from templates/integration-tests/js-tests/ava.config.cjs rename to templates/sandbox-tests/sandbox-ts/ava.config.cjs diff --git a/templates/integration-tests/js-tests/package-lock.json b/templates/sandbox-tests/sandbox-ts/package-lock.json similarity index 100% rename from templates/integration-tests/js-tests/package-lock.json rename to templates/sandbox-tests/sandbox-ts/package-lock.json diff --git a/templates/integration-tests/js-tests/package.json b/templates/sandbox-tests/sandbox-ts/package.json similarity index 56% rename from templates/integration-tests/js-tests/package.json rename to templates/sandbox-tests/sandbox-ts/package.json index deffe68f4..2e88698b1 100644 --- a/templates/integration-tests/js-tests/package.json +++ b/templates/sandbox-tests/sandbox-ts/package.json @@ -9,12 +9,12 @@ "test": "ava" }, "devDependencies": { - "@types/bn.js": "^5.1.0", - "@types/node": "^18.6.2", - "ava": "^4.2.0", - "near-workspaces": "^3.2.1", - "ts-node": "^10.8.0", - "typescript": "^4.7.2" + "@types/bn.js": "^5.1.4", + "@types/node": "^20.8.10", + "ava": "^5.3.1", + "near-workspaces": "^3.3.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" }, "dependencies": {} } diff --git a/templates/integration-tests/js-tests/src/main.ava.ts b/templates/sandbox-tests/sandbox-ts/src/main.ava.ts similarity index 100% rename from templates/integration-tests/js-tests/src/main.ava.ts rename to templates/sandbox-tests/sandbox-ts/src/main.ava.ts diff --git a/test/e2e.sh b/test/e2e.sh index f8a1e2f02..af3af0e62 100755 --- a/test/e2e.sh +++ b/test/e2e.sh @@ -25,13 +25,13 @@ test () { } ## CONTRACT:JS SANDBOX:JS -scaffold js none js -test "js_none_js" +scaffold js none ts +test "js_none_ts" -## CONTRACT:RUST SANDBOX:JS -scaffold rust none js -test "rust_none_js" +## CONTRACT:RUST SANDBOX:TS +scaffold rust none ts +test "rust_none_ts" ### CONTRACT:RUST SANDBOX:RUST -scaffold rust none rust -test "rust_none_rust" \ No newline at end of file +scaffold rust none rs +test "rust_none_rs" \ No newline at end of file diff --git a/test/make.test.ts b/test/make.test.ts index 666fa11ec..12838d7ff 100644 --- a/test/make.test.ts +++ b/test/make.test.ts @@ -6,8 +6,8 @@ import {Contract, Frontend, TestingFramework} from '../src/types'; describe('create', () => { const contracts: Contract[] = ['js', 'rust', 'none']; - const frontends: Frontend[] = ['gateway', 'vanilla', 'none']; - const tests: TestingFramework[] = ['js', 'rust']; + const frontends: Frontend[] = ['next', 'vanilla', 'none']; + const tests: TestingFramework[] = ['ts', 'rs']; // all combinations of the above const testMatrix = contracts.flatMap(c => frontends.flatMap(f => tests.map(t => ([c, f, t])))); diff --git a/test/user-input.test.ts b/test/user-input.test.ts index 72842ee59..5e25734f6 100644 --- a/test/user-input.test.ts +++ b/test/user-input.test.ts @@ -24,7 +24,7 @@ describe('messages', () => { show.successContractToText('js'); show.successContractToText('rust'); - show.successFrontendToText('gateway'); + show.successFrontendToText('next'); show.successFrontendToText('vanilla'); show.successFrontendToText('none'); @@ -40,8 +40,8 @@ describe('messages', () => { describe('test success message', () => { let showSpy; const contracts: Contract[] = ['js', 'rust', 'none']; - const frontends: Frontend[] = ['gateway', 'vanilla', 'none']; - const tests: TestingFramework[] = ['js', 'rust']; + const frontends: Frontend[] = ['next', 'vanilla', 'none']; + const tests: TestingFramework[] = ['ts', 'rs']; const install = [true, false]; // all combinations of the above const testMatrix = contracts.flatMap(c => frontends.flatMap(f => tests.flatMap(t => install.map(i => ([c, f, t, i])))));