mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 13:21:44 +01:00
Create partial sign up flow
remove cypress tests
This commit is contained in:
parent
d2f307642a
commit
3a98501c93
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
dist
|
||||
node_modules
|
||||
cypress/videos
|
||||
stats.html
|
||||
|
@ -1,12 +0,0 @@
|
||||
import { defineConfig } from "cypress";
|
||||
|
||||
export default defineConfig({
|
||||
viewportWidth: 1200,
|
||||
viewportHeight: 800,
|
||||
e2e: {
|
||||
baseUrl: "http://localhost:5173",
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
});
|
@ -1,127 +0,0 @@
|
||||
describe("Embeds", () => {
|
||||
describe("hashtags", () => {
|
||||
it('should handle uppercase hashtags and ","', () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqsrj5ns6wva3fcghlyx0hp7hhajqtqk3kuckp7xhhscrm4jl7futegpz9mhxue69uhkummnw3e82efwvdhk6qgswaehxw309ahx7um5wgh8w6twv5pkpt8l",
|
||||
);
|
||||
|
||||
cy.findByRole("link", { name: "#Japan" }).should("be.visible");
|
||||
cy.findByRole("link", { name: "#kyudo" }).should("be.visible");
|
||||
cy.findByRole("link", { name: "#Shiseikan" }).should("be.visible");
|
||||
cy.findByRole("link", { name: "#Nostrasia" }).should("be.visible");
|
||||
});
|
||||
});
|
||||
|
||||
describe("links", () => {
|
||||
it("embed trustless.computer links", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqsfn2mv3pe2v7jak4r5wnyengt36t0rx26w04hgysrmtpml8jnlk5cprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2qgawaehxw309ahx7um5wgkhqatz9emk2mrvdaexgetj9ehx2aq2wry06",
|
||||
);
|
||||
|
||||
cy.get('[href="https://trustless.computer/"]').should("be.visible");
|
||||
cy.get(
|
||||
'[href="https://mempool.space/tx/461c6f56015c94d74837b68c9d08f4b80e7db7ca1e5ac4c53d9aa8c76b667672"]',
|
||||
).should("be.visible");
|
||||
});
|
||||
|
||||
it("embeds links", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqsvg6kt4hl79qpp5p673g7ref6r0c5jvp4yys7mmvs4m50t30sy9dgpz9mhxue69uhkummnw3e82efwvdhk6qgjwaehxw309aex2mrp0yhxvdm69e5k7r3xlpe",
|
||||
);
|
||||
|
||||
cy.get('[href="https://getalby.com/"]').should("exist");
|
||||
cy.get('[href="https://lightningaddress.com/"]').should("exist");
|
||||
cy.get('[href="https://snort.social/"]').should("exist");
|
||||
cy.get('[href="http://damus.io/"]').should("exist");
|
||||
cy.get('[href="https://vida.live/"]').should("exist");
|
||||
});
|
||||
|
||||
it("embeds simplex.chat links", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqsymds0vlpp4f5s0dckjf4qz283pdsen0rmx8lu7ct6hpnxag2hpacpremhxue69uhkummnw3ez6un9d3shjtnwda4k7arpwfhjucm0d5q3qamnwvaz7tmwdaehgu3wwa5kueghxyq76",
|
||||
);
|
||||
|
||||
cy.get(
|
||||
'[href="https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU%3D%40smp8.simplex.im%2FVlHiRmia02CDgga7w-uNb2FQZTZsj3UR%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAd2GEWU9Zjrljhw8O4FldcxrqehkDWezXl-cWD-VkeEw%253D%26srv%3Dbeccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion"]',
|
||||
).should("be.visible");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Nostr links", () => {
|
||||
it("should embed noub1...", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqsd5yw7sntqfc4e7u4aempvgctry2plz653t9gpf97ctk5vc0ftskgpz3mhxue69uhhyetvv9ujuerpd46hxtnfduq3zamnwvaz7tmwdaehgun4v5hxxmmdfxdj3a",
|
||||
);
|
||||
cy.contains("Alby team");
|
||||
|
||||
cy.get(".chakra-card")
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('[href="#/u/npub13sajvl5ak6cpz4ycesl0e5v869r5sey5pt50l9mcy6uas0fqtpmscth4np"]').should("be.visible");
|
||||
cy.get('[href="#/u/npub167n5w6cj2wseqtmk26zllc7n28uv9c4vw28k2kht206vnghe5a7stgzu3r"]').should("be.visible");
|
||||
|
||||
// make sure the leading @ is removed
|
||||
cy.get(".chakra-card__body").should("not.contain.text", "@@");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("youtube", () => {
|
||||
it("should embed playlists", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqs8w6e63smpr5ccmz4l0w5pvnkp6r7z2fxaadjwu2g74y95pl9xv0cpzpmhxue69uhkummnw3ezuamfdejszrthwden5te0dehhxtnvdakqqkgf54",
|
||||
);
|
||||
|
||||
cy.findByTitle(/youtube video player/i).should("be.visible");
|
||||
cy.findByTitle(/youtube video player/i).should("have.attr", "src");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Music", () => {
|
||||
it("should handle wavlake links", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqsve4ud5v8gjds2f2h7exlmjvhqayu4s520pge7frpwe22wezny0pcpp4mhxue69uhkummn9ekx7mqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2mxs3z0",
|
||||
);
|
||||
cy.findByTitle("Wavlake Embed").should("be.visible");
|
||||
});
|
||||
|
||||
it("should handle spotify links", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqsx0lz7m72qzq499exwhnfszvgwea8tv38x9wkv32yhkmwwmhgs7jgprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk25m3sln",
|
||||
);
|
||||
cy.findByTitle("Spotify List Embed").should("exist");
|
||||
|
||||
cy.visit(
|
||||
"#/n/nevent1qqsqxkmz49hydf8ppa9k6x6zrcq7m4evhhlye0j3lcnz8hrl2q6np4spz3mhxue69uhhyetvv9ujuerpd46hxtnfdult02qz",
|
||||
);
|
||||
cy.findByTitle("Spotify Embed").should("exist");
|
||||
});
|
||||
|
||||
it("should handle apple music links", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqs9kqt9d7r4zjpawcyl82x5qsn4hals4wn294dv95knrahs4mggwasprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2whhzvz",
|
||||
);
|
||||
cy.findByTitle("Apple Music Embed").should("exist");
|
||||
|
||||
cy.visit(
|
||||
"#/n/nevent1qqszyrz4uug75j4086kj4f8peg3g0v8g9f04zjxplnpq0uxljtthggqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2aeexmq",
|
||||
);
|
||||
cy.findByTitle("Apple Music List Embed").should("exist");
|
||||
});
|
||||
|
||||
it("should handle Tidal playlist links", () => {
|
||||
cy.visit("#/n/nevent1qqsg4d6rvg3te0y7sa0xp8r2rgcrnqyp2jmddzm4ufnmqs36aa2247qpp4mhxue69uhkummn9ekx7mqacwd3t");
|
||||
cy.findByTitle("Tidal List Embed").should("be.visible");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Emoji", () => {
|
||||
it("should embed emojis", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqsdj7k47uh4z0ypl2m29lvd4ar9zpf6dcy7ls0q6g6qctnxfj5n3pcpzpmhxue69uhkummnw3ezuamfdejszrthwden5te0dehhxtnvdakqdyqlpq",
|
||||
);
|
||||
|
||||
cy.findByRole("img", { name: /pepeD/i }).should("be.visible");
|
||||
});
|
||||
});
|
||||
});
|
@ -1,68 +0,0 @@
|
||||
describe("Login view", () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("#/login");
|
||||
|
||||
cy.window().then(($win) => {
|
||||
cy.stub($win, "prompt").returns("pass");
|
||||
});
|
||||
});
|
||||
|
||||
it("login with nip05", () => {
|
||||
cy.intercept("get", "https://hzrd149.com/.well-known/nostr.json?name=_", {
|
||||
names: {
|
||||
_: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
|
||||
},
|
||||
relays: {
|
||||
"266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5": ["wss://nostrue.com"],
|
||||
},
|
||||
});
|
||||
|
||||
cy.findByRole("link", { name: /nip-05/i }).click();
|
||||
|
||||
cy.findByRole("textbox", { name: /nip-05/i }).type("_@hzrd149.com");
|
||||
cy.contains(/found 1 relays/i);
|
||||
cy.findByRole("button", { name: /login/i }).click();
|
||||
|
||||
cy.findByRole("button", { name: "Home" }).should("be.visible");
|
||||
});
|
||||
|
||||
it("login with npub", () => {
|
||||
cy.findByRole("link", { name: /npub/i }).click();
|
||||
cy.findByRole("textbox", { name: /npub/i }).type("npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr");
|
||||
cy.findByRole("combobox", { name: /bootstrap relay/i })
|
||||
.clear()
|
||||
.type("wss://nostrue.com");
|
||||
cy.findByRole("button", { name: /login/i }).click();
|
||||
|
||||
cy.findByRole("button", { name: "Home" }).should("be.visible");
|
||||
});
|
||||
|
||||
it("login with new nsec", () => {
|
||||
cy.findByRole("link", { name: /nsec/i }).click();
|
||||
cy.findByRole("button", { name: /generate/i }).click();
|
||||
cy.findByRole("combobox", { name: /bootstrap relay/i })
|
||||
.clear()
|
||||
.type("wss://nostrue.com");
|
||||
cy.findByRole("button", { name: /login/i }).click();
|
||||
|
||||
cy.findByRole("button", { name: "Home" }).should("be.visible");
|
||||
});
|
||||
|
||||
it("should redirect after login", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqs88gdxv36qsjfwr66k7wxuq9r2tg8rsdcnfkcqdg4sc6vlnsma98qpzpmhxue69uhkummnw3ezuamfdejsz9rhwden5te0wfjkccte9ejxzmt4wvhxjmccew89d",
|
||||
);
|
||||
|
||||
cy.findByRole("link", { name: /login/i }).click();
|
||||
|
||||
cy.findByRole("link", { name: /nsec/i }).click();
|
||||
cy.findByRole("button", { name: /generate/i }).click();
|
||||
cy.findByRole("combobox", { name: /bootstrap relay/i })
|
||||
.clear()
|
||||
.type("wss://nostrue.com");
|
||||
cy.findByRole("button", { name: /login/i }).click();
|
||||
|
||||
// should be redirect to note
|
||||
cy.contains(/GM, and happy bday to your son/i);
|
||||
});
|
||||
});
|
@ -1,16 +0,0 @@
|
||||
describe("Profile view", () => {
|
||||
it("should load a rss feed profile", () => {
|
||||
cy.visit(
|
||||
"#/u/nprofile1qqsp6hxqjatvxtesgszs8aee0fcjccxa3ef3mzjva4uv2yr5lucp6jcpzemhxue69uhhyumnd3shjtnwdaehgu3wd4hk2s8c5un",
|
||||
);
|
||||
|
||||
cy.contains("fjsmu");
|
||||
cy.contains("https://rsshub.app/pixiv/user/7569500@rsslay.nostr.moe");
|
||||
});
|
||||
|
||||
it("should load PABLOF7z", () => {
|
||||
cy.visit("#/u/npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft");
|
||||
|
||||
cy.contains("npub1l2vyh...3afqutajft");
|
||||
});
|
||||
});
|
@ -1,22 +0,0 @@
|
||||
describe("No account", () => {
|
||||
describe("note view", () => {
|
||||
it("should fetch and render note", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqs84hwdlls703w4yf66qsszxjqfc0xselfxrzr6n4qp40vzdnczragpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5jcwczn",
|
||||
);
|
||||
|
||||
cy.get(".chakra-card")
|
||||
.first()
|
||||
.within(() => {
|
||||
// check for note content
|
||||
cy.contains('I didn\'t know someone had taken the "rsslay" idea and made it good');
|
||||
|
||||
// check for author name
|
||||
cy.get(".chakra-card__header .chakra-heading .chakra-link").should("not.contain", "npub");
|
||||
});
|
||||
|
||||
// check for multiple replies
|
||||
cy.get(".chakra-card").should("have.length.above", 2);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,69 +0,0 @@
|
||||
describe("Search", () => {
|
||||
describe("Events", () => {
|
||||
const links: [string, RegExp][] = [
|
||||
[
|
||||
"nostr:nevent1qqsvg6kt4hl79qpp5p673g7ref6r0c5jvp4yys7mmvs4m50t30sy9dgpp4mhxue69uhkummn9ekx7mqpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet59dl66z",
|
||||
/Nostr zaps - a guide/i,
|
||||
],
|
||||
["nostr:note10twumllpulza2gn45ppqydyqns7dpn7jvxy8482qr27cym8sy86sgxe3c8", /someone had taken/i],
|
||||
];
|
||||
|
||||
for (const [link, regexp] of links) {
|
||||
it(`should handle ${link}`, () => {
|
||||
cy.visit("#/search");
|
||||
cy.findByRole("searchbox").type(link, { delay: 0 }).type("{enter}");
|
||||
|
||||
cy.contains(regexp).should("be.visible");
|
||||
});
|
||||
}
|
||||
|
||||
for (const [link, regexp] of links) {
|
||||
const withoutPrefix = link.replace("nostr:", "");
|
||||
it(`should handle ${withoutPrefix}`, () => {
|
||||
cy.visit("#/search");
|
||||
cy.findByRole("searchbox").type(link, { delay: 0 }).type("{enter}");
|
||||
|
||||
cy.contains(regexp).should("be.visible");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("Profiles", () => {
|
||||
const profiles: [string, RegExp][] = [
|
||||
[
|
||||
"nostr:nprofile1qqsp2alytxwazryxxjv0u0pqhkp247hc9xjetn5rch8c4s6xx5cmpxcpzpmhxue69uhkummnw3ezuamfdejsz9nhwden5te0v96xcctn9ehx7um5wghxcctwvs6ymk33",
|
||||
/npub1z4m7g\.\.\.kzdsxana6p/i,
|
||||
],
|
||||
["nostr:npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6", /npub180cvv\.\.\.gkwsyjh6w6/i],
|
||||
];
|
||||
|
||||
for (const [search, regexp] of profiles) {
|
||||
it(`should handle ${search}`, () => {
|
||||
cy.visit("#/search");
|
||||
cy.findByRole("searchbox").type(search, { delay: 0 }).type("{enter}");
|
||||
|
||||
cy.contains(regexp).should("be.visible");
|
||||
});
|
||||
}
|
||||
|
||||
for (const [search, regexp] of profiles) {
|
||||
const withoutPrefix = search.replace("nostr:", "");
|
||||
it(`should handle ${withoutPrefix}`, () => {
|
||||
cy.visit("#/search");
|
||||
cy.findByRole("searchbox").type(search, { delay: 0 }).type("{enter}");
|
||||
|
||||
cy.contains(regexp).should("be.visible");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("Hashtag", () => {
|
||||
it("should redirect to hashtag view", () => {
|
||||
cy.visit("#/search");
|
||||
cy.findByRole("searchbox").type("#bitcoin").type("{enter}");
|
||||
|
||||
cy.url().should("contain", "/t/bitcoin");
|
||||
cy.contains("#bitcoin");
|
||||
});
|
||||
});
|
||||
});
|
@ -1,17 +0,0 @@
|
||||
describe("Thread", () => {
|
||||
it("should handle quote notes with e tags correctly", () => {
|
||||
cy.visit(
|
||||
"#/n/nevent1qqsx2lnyuke6vmsrz9fdrd6uwjy0g0e9l6menfgdj5truugkh9qmkkgpzpmhxue69uhkummnw3ezuamfdejszrthwden5te0dehhxtnvdakqgc9md6",
|
||||
);
|
||||
|
||||
// find first note
|
||||
cy.get(".chakra-card")
|
||||
.first()
|
||||
.within(() => {
|
||||
// get quoted note
|
||||
cy.get(".chakra-card").within(() => {
|
||||
cy.contains(/looking for people to send money/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,53 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import "@testing-library/cypress/add-commands";
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
loginWithNewUser(): Chainable<void>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add("loginWithNewUser", () => {
|
||||
cy.visit("/login");
|
||||
|
||||
cy.window().then(($win) => {
|
||||
cy.stub($win, "prompt").returns("pass");
|
||||
});
|
||||
|
||||
cy.findByRole("link", { name: /nsec/i }).click();
|
||||
cy.findByRole("button", { name: /generate/i }).click();
|
||||
cy.findByRole("combobox", { name: /bootstrap relay/i })
|
||||
.clear()
|
||||
.type("wss://nostrue.com", { delay: 0 });
|
||||
cy.findByRole("button", { name: /login/i }).click();
|
||||
|
||||
cy.findByRole("button", { name: "Home" }).should("be.visible");
|
||||
});
|
@ -1,31 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import "./commands";
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
beforeEach(() => {
|
||||
cy.clearAllLocalStorage();
|
||||
|
||||
// remove the database for every test
|
||||
new Promise((res, rej) => {
|
||||
const request = window.indexedDB.deleteDatabase("storage");
|
||||
request.onsuccess = res;
|
||||
request.onerror = rej;
|
||||
});
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "../tsconfig.json",
|
||||
"include": ["../next-env.d.ts", "../cypress.config.ts", "**/*.ts", "**/*.tsx"],
|
||||
"compilerOptions": {
|
||||
"types": ["cypress", "@testing-library/cypress", "next"]
|
||||
}
|
||||
}
|
@ -8,8 +8,6 @@
|
||||
"dev": "VITE_APP_VERSION=development vite serve",
|
||||
"build": "tsc --project tsconfig.json && vite build",
|
||||
"format": "prettier --ignore-path .prettierignore -w .",
|
||||
"e2e": "cypress open",
|
||||
"test": "cypress run --e2e --browser=chrome",
|
||||
"analyze": "npx vite-bundle-visualizer -o ./stats.html",
|
||||
"build-icons": "node ./scripts/build-icons.mjs"
|
||||
},
|
||||
@ -67,7 +65,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.26.2",
|
||||
"@testing-library/cypress": "^9.0.0",
|
||||
"@types/chroma-js": "^2.4.1",
|
||||
"@types/debug": "^4.1.8",
|
||||
"@types/identicon.js": "^2.3.1",
|
||||
@ -80,7 +77,6 @@
|
||||
"@types/webscopeio__react-textarea-autocomplete": "^4.7.2",
|
||||
"@vitejs/plugin-react": "^4.0.4",
|
||||
"camelcase": "^8.0.0",
|
||||
"cypress": "^12.17.4",
|
||||
"prettier": "^3.0.2",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.4.9",
|
||||
|
17
src/app.tsx
17
src/app.tsx
@ -19,11 +19,11 @@ import NotificationsView from "./views/notifications";
|
||||
import DirectMessagesView from "./views/messages";
|
||||
import DirectMessageChatView from "./views/messages/chat";
|
||||
|
||||
import LoginView from "./views/login";
|
||||
import LoginStartView from "./views/login/start";
|
||||
import LoginNpubView from "./views/login/npub";
|
||||
import LoginNip05View from "./views/login/nip05";
|
||||
import LoginNsecView from "./views/login/nsec";
|
||||
import LoginView from "./views/signin";
|
||||
import LoginStartView from "./views/signin/start";
|
||||
import LoginNpubView from "./views/signin/npub";
|
||||
import LoginNip05View from "./views/signin/nip05";
|
||||
import LoginNsecView from "./views/signin/nsec";
|
||||
|
||||
import UserView from "./views/user";
|
||||
import UserNotesTab from "./views/user/notes";
|
||||
@ -67,6 +67,7 @@ import RelayView from "./views/relays/relay";
|
||||
import RelayReviewsView from "./views/relays/reviews";
|
||||
import PopularRelaysView from "./views/relays/popular";
|
||||
import UserTracksTab from "./views/user/tracks";
|
||||
import SignupView from "./views/signup";
|
||||
|
||||
const ToolsHomeView = React.lazy(() => import("./views/tools"));
|
||||
const NetworkView = React.lazy(() => import("./views/tools/network"));
|
||||
@ -122,7 +123,7 @@ const RootPage = () => {
|
||||
|
||||
const router = createHashRouter([
|
||||
{
|
||||
path: "login",
|
||||
path: "signin",
|
||||
element: <LoginView />,
|
||||
children: [
|
||||
{ path: "", element: <LoginStartView /> },
|
||||
@ -131,6 +132,10 @@ const router = createHashRouter([
|
||||
{ path: "nsec", element: <LoginNsecView /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "signup",
|
||||
element: <SignupView />,
|
||||
},
|
||||
{
|
||||
path: "streams/:naddr",
|
||||
element: (
|
||||
|
@ -83,7 +83,7 @@ export default function AccountSwitcher() {
|
||||
leftIcon={<AddIcon />}
|
||||
onClick={() => {
|
||||
accountService.logout();
|
||||
navigate("/login", { state: { from: location.pathname } });
|
||||
navigate("/signin", { state: { from: location.pathname } });
|
||||
}}
|
||||
>
|
||||
Add Account
|
||||
|
@ -64,8 +64,8 @@ export default function DesktopSideNav(props: Omit<FlexProps, "children">) {
|
||||
<NavItems />
|
||||
<Box h="4" />
|
||||
{!account && (
|
||||
<Button as={RouterLink} to="/login" state={{ from: location.pathname }} colorScheme="primary" w="full">
|
||||
Login
|
||||
<Button as={RouterLink} to="/signin" state={{ from: location.pathname }} colorScheme="primary" w="full">
|
||||
Sign in
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
|
@ -45,8 +45,8 @@ export default function MobileSideDrawer({ ...props }: Omit<DrawerProps, "childr
|
||||
<NavItems />
|
||||
<Box h="2" />
|
||||
{!account && (
|
||||
<Button as={RouterLink} to="/login" colorScheme="primary">
|
||||
Login
|
||||
<Button as={RouterLink} to="/signin" colorScheme="primary">
|
||||
Sign in
|
||||
</Button>
|
||||
)}
|
||||
</DrawerBody>
|
||||
|
@ -42,14 +42,23 @@ export default function PeopleListSelection({
|
||||
<MenuOptionGroup value={selected} onChange={handleSelect} type="radio">
|
||||
{account && <MenuItemOption value="following">Following</MenuItemOption>}
|
||||
{!hideGlobalOption && <MenuItemOption value="global">Global</MenuItemOption>}
|
||||
{lists.length > 0 && <MenuDivider />}
|
||||
{lists
|
||||
.filter((l) => l.kind === PEOPLE_LIST_KIND)
|
||||
.map((list) => (
|
||||
<MenuItemOption key={getEventCoordinate(list)} value={getEventCoordinate(list)} isTruncated maxW="90vw">
|
||||
{getListName(list)}
|
||||
</MenuItemOption>
|
||||
))}
|
||||
{lists.length > 0 && (
|
||||
<>
|
||||
<MenuDivider />
|
||||
{lists
|
||||
.filter((l) => l.kind === PEOPLE_LIST_KIND)
|
||||
.map((list) => (
|
||||
<MenuItemOption
|
||||
key={getEventCoordinate(list)}
|
||||
value={getEventCoordinate(list)}
|
||||
isTruncated
|
||||
maxW="90vw"
|
||||
>
|
||||
{getListName(list)}
|
||||
</MenuItemOption>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</MenuOptionGroup>
|
||||
{favoriteLists.length > 0 && (
|
||||
<>
|
||||
|
@ -21,5 +21,6 @@ export default function useUserLists(pubkey?: string, additionalRelays: string[]
|
||||
{ enabled: !!pubkey, eventFilter },
|
||||
);
|
||||
|
||||
return useSubject(timeline.timeline);
|
||||
const lists = useSubject(timeline.timeline);
|
||||
return pubkey ? lists : [];
|
||||
}
|
||||
|
@ -43,12 +43,13 @@ function useListCoordinate(listId: ListId) {
|
||||
export type PeopleListProviderProps = PropsWithChildren & {
|
||||
initList?: ListId;
|
||||
};
|
||||
export default function PeopleListProvider({ children, initList = "following" }: PeopleListProviderProps) {
|
||||
export default function PeopleListProvider({ children, initList }: PeopleListProviderProps) {
|
||||
const account = useCurrentAccount();
|
||||
const [params, setParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const selected = params.get("people") || (initList as ListId);
|
||||
const selected = params.get("people") || (initList as ListId) || (account ? "following" : "global");
|
||||
const setSelected = useCallback(
|
||||
(value: ListId) => {
|
||||
const newParams = new URLSearchParams(location.search);
|
||||
|
@ -27,15 +27,9 @@ export default function RequireCurrentAccount({ children }: { children: JSX.Elem
|
||||
if (!account)
|
||||
return (
|
||||
<Flex direction="column" w="full" h="full" alignItems="center" justifyContent="center" gap="4">
|
||||
<Heading size="md">You must be logged in to use this view</Heading>
|
||||
<Button
|
||||
as={Link}
|
||||
to="/login"
|
||||
state={{ from: location.pathname }}
|
||||
colorScheme="primary"
|
||||
rightIcon={<ExternalLinkIcon />}
|
||||
>
|
||||
Login
|
||||
<Heading size="md">You must be signed in to use this view</Heading>
|
||||
<Button as={Link} to="/signin" state={{ from: location.pathname }} colorScheme="primary">
|
||||
Sign in
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
|
@ -95,6 +95,10 @@ class AccountService {
|
||||
}
|
||||
|
||||
logout() {
|
||||
if (this.current.value) {
|
||||
this.removeAccount(this.current.value.pubkey);
|
||||
}
|
||||
|
||||
this.current.next(null);
|
||||
this.isGhost.next(false);
|
||||
localStorage.removeItem("lastAccount");
|
||||
|
@ -34,6 +34,7 @@ import Timestamp from "../../components/timestamp";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import BadgeAwardCard from "./components/badge-award-card";
|
||||
import TimelineLoader from "../../classes/timeline-loader";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
|
||||
function BadgeActivityTab({ timeline }: { timeline: TimelineLoader }) {
|
||||
const awards = useSubject(timeline.timeline);
|
||||
@ -43,7 +44,9 @@ function BadgeActivityTab({ timeline }: { timeline: TimelineLoader }) {
|
||||
<Flex direction="column" gap="4">
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{awards.map((award) => (
|
||||
<BadgeAwardCard key={award.id} award={award} showImage={false} />
|
||||
<ErrorBoundary key={award.id}>
|
||||
<BadgeAwardCard award={award} showImage={false} />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</IntersectionObserverProvider>
|
||||
</Flex>
|
||||
|
@ -12,6 +12,7 @@ import useSubject from "../../hooks/use-subject";
|
||||
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import BadgeAwardCard from "./components/badge-award-card";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
|
||||
function BadgesPage() {
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
@ -47,7 +48,9 @@ function BadgesPage() {
|
||||
</Flex>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{awards.map((award) => (
|
||||
<BadgeAwardCard key={award.id} award={award} />
|
||||
<ErrorBoundary key={award.id}>
|
||||
<BadgeAwardCard award={award} />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</IntersectionObserverProvider>
|
||||
</VerticalPageLayout>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Avatar, Flex, Heading } from "@chakra-ui/react";
|
||||
import { Avatar, Center, Flex, Heading } from "@chakra-ui/react";
|
||||
import { Navigate, Outlet, useLocation } from "react-router-dom";
|
||||
import { ReloadPrompt } from "../../components/reload-prompt";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
@ -13,20 +13,15 @@ export default function LoginView() {
|
||||
return (
|
||||
<>
|
||||
<ReloadPrompt />
|
||||
<Flex
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
gap="4"
|
||||
height="100%"
|
||||
padding="4"
|
||||
overflowX="hidden"
|
||||
overflowY="auto"
|
||||
>
|
||||
<Avatar src="/apple-touch-icon.png" size="lg" flexShrink={0} />
|
||||
<Heading>noStrudel</Heading>
|
||||
<Outlet />
|
||||
</Flex>
|
||||
<Center w="full" h="full">
|
||||
<Flex direction="column" alignItems="center" gap="2" maxW="sm" w="full" mx="4">
|
||||
<Avatar src="/apple-touch-icon.png" size="lg" flexShrink={0} />
|
||||
<Heading size="lg" mb="2">
|
||||
Sign in
|
||||
</Heading>
|
||||
<Outlet />
|
||||
</Flex>
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
}
|
@ -89,7 +89,7 @@ export default function LoginNip05View() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit} minWidth="350">
|
||||
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit} minWidth="350" w="full">
|
||||
<FormControl>
|
||||
<FormLabel>Enter user NIP-05 id</FormLabel>
|
||||
<InputGroup>
|
@ -27,7 +27,7 @@ export default function LoginNpubView() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit} minWidth="350">
|
||||
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit} minWidth="350" w="full">
|
||||
<FormControl>
|
||||
<FormLabel>Enter user npub</FormLabel>
|
||||
<Input type="text" placeholder="npub1" isRequired value={npub} onChange={(e) => setNpub(e.target.value)} />
|
@ -77,11 +77,11 @@ export default function LoginNsecView() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit} minWidth="350">
|
||||
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit} minWidth="350" w="full">
|
||||
<Alert status="warning" maxWidth="30rem">
|
||||
<AlertIcon />
|
||||
<Box>
|
||||
<AlertTitle>Using nsec keys is insecure.</AlertTitle>
|
||||
<AlertTitle>Using secret keys is insecure</AlertTitle>
|
||||
<AlertDescription>
|
||||
You should use a browser extension like{" "}
|
||||
<Link isExternal href="https://getalby.com/" target="_blank">
|
@ -1,28 +1,19 @@
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertIcon,
|
||||
AlertTitle,
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Heading,
|
||||
Spinner,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import { Badge, Button, Flex, Spinner, Text, useDisclosure, useToast } from "@chakra-ui/react";
|
||||
import { Link as RouterLink, useLocation } from "react-router-dom";
|
||||
import AccountCard from "./components/account-card";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
|
||||
import accountService from "../../services/account";
|
||||
import Key01 from "../../components/icons/key-01";
|
||||
import ChevronDown from "../../components/icons/chevron-down";
|
||||
import ChevronUp from "../../components/icons/chevron-up";
|
||||
|
||||
export default function LoginStartView() {
|
||||
const location = useLocation();
|
||||
const toast = useToast();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const accounts = useSubject(accountService.accounts);
|
||||
const advanced = useDisclosure();
|
||||
|
||||
const loginWithExtension = async () => {
|
||||
const signinWithExtension = async () => {
|
||||
if (window.nostr) {
|
||||
try {
|
||||
setLoading(true);
|
||||
@ -60,37 +51,43 @@ export default function LoginStartView() {
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" flexShrink={0} alignItems="center">
|
||||
<Alert status="warning" maxWidth="30rem">
|
||||
<AlertIcon />
|
||||
<Box>
|
||||
<AlertTitle>This app is half-baked.</AlertTitle>
|
||||
<AlertDescription>There are bugs and things will break.</AlertDescription>
|
||||
</Box>
|
||||
</Alert>
|
||||
<Button onClick={loginWithExtension} colorScheme="primary">
|
||||
Use browser extension (NIP-07)
|
||||
<Button onClick={signinWithExtension} leftIcon={<Key01 boxSize={6} />} w="sm" colorScheme="primary">
|
||||
Sign in with extension
|
||||
</Button>
|
||||
<Button as={RouterLink} to="./nip05" state={location.state}>
|
||||
Login with Nip-05 Id
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={advanced.onToggle}
|
||||
mt="2"
|
||||
w="sm"
|
||||
rightIcon={advanced.isOpen ? <ChevronUp /> : <ChevronDown />}
|
||||
>
|
||||
Show Advanced
|
||||
</Button>
|
||||
<Button as={RouterLink} to="./npub" state={location.state}>
|
||||
Login with pubkey key (npub)
|
||||
</Button>
|
||||
<Button as={RouterLink} to="./nsec" state={location.state}>
|
||||
Login with secret key (nsec)
|
||||
</Button>
|
||||
{accounts.length > 0 && (
|
||||
{advanced.isOpen && (
|
||||
<>
|
||||
<Heading size="md" mt="4">
|
||||
Accounts:
|
||||
</Heading>
|
||||
<Flex gap="2" direction="column" minW={300}>
|
||||
{accounts.map((account) => (
|
||||
<AccountCard key={account.pubkey} account={account} />
|
||||
))}
|
||||
</Flex>
|
||||
<Button as={RouterLink} to="./nip05" state={location.state} w="sm">
|
||||
NIP05
|
||||
<Badge ml="2" colorScheme="blue">
|
||||
read-only
|
||||
</Badge>
|
||||
</Button>
|
||||
<Button as={RouterLink} to="./npub" state={location.state} w="sm">
|
||||
public key (npub)
|
||||
<Badge ml="2" colorScheme="blue">
|
||||
read-only
|
||||
</Badge>
|
||||
</Button>
|
||||
<Button as={RouterLink} to="./nsec" state={location.state} w="sm">
|
||||
secret key (nsec)
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Text fontWeight="bold" mt="4">
|
||||
Don't have an account?
|
||||
</Text>
|
||||
<Button as={RouterLink} to="/signup" state={location.state}>
|
||||
Sign up
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
}
|
222
src/views/signup/index.tsx
Normal file
222
src/views/signup/index.tsx
Normal file
@ -0,0 +1,222 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
Center,
|
||||
Flex,
|
||||
FlexProps,
|
||||
Heading,
|
||||
Input,
|
||||
SimpleGrid,
|
||||
Text,
|
||||
VisuallyHiddenInput,
|
||||
} from "@chakra-ui/react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Link as RouterLink, useLocation } from "react-router-dom";
|
||||
import { useSet } from "react-use";
|
||||
|
||||
import { Kind0ParsedContent } from "../../helpers/user-metadata";
|
||||
import { useRelayInfo } from "../../hooks/use-relay-info";
|
||||
import { RelayFavicon } from "../../components/relay-favicon";
|
||||
import ImagePlus from "../../components/icons/image-plus";
|
||||
|
||||
const containerProps: FlexProps = {
|
||||
w: "full",
|
||||
maxW: "sm",
|
||||
mx: "4",
|
||||
alignItems: "center",
|
||||
direction: "column",
|
||||
};
|
||||
const AppIcon = () => <Avatar src="/apple-touch-icon.png" size="lg" flexShrink={0} />;
|
||||
|
||||
function NameStep({ onSubmit }: { onSubmit: (metadata: Kind0ParsedContent) => void }) {
|
||||
const location = useLocation();
|
||||
const { register, handleSubmit } = useForm({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
},
|
||||
mode: "all",
|
||||
});
|
||||
const submit = handleSubmit((values) => {
|
||||
const displayName = values.name;
|
||||
const username = values.name.toLocaleLowerCase().replaceAll(/(\p{Z}|\p{P}|\p{C}|\p{M})/gu, "_");
|
||||
|
||||
onSubmit({
|
||||
name: username,
|
||||
display_name: displayName,
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex as="form" gap="2" onSubmit={submit} {...containerProps}>
|
||||
<AppIcon />
|
||||
<Heading size="lg" mb="2">
|
||||
Sign up
|
||||
</Heading>
|
||||
<Text>What should we call you?</Text>
|
||||
<Input placeholder="Jane" w="full" mb="2" {...register("name", { required: true })} autoComplete="off" />
|
||||
<Button w="full" colorScheme="primary" mb="4">
|
||||
Next
|
||||
</Button>
|
||||
<Text fontWeight="bold">Already have an account?</Text>
|
||||
<Button as={RouterLink} to="/signin" state={location.state}>
|
||||
Sign in
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileImageStep({ displayName, onSubmit }: { displayName?: string; onSubmit: (picture?: File) => void }) {
|
||||
const [file, setFile] = useState<File>();
|
||||
const uploadRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const [preview, setPreview] = useState("");
|
||||
useEffect(() => {
|
||||
if (file) {
|
||||
const url = URL.createObjectURL(file);
|
||||
setPreview(url);
|
||||
return () => URL.revokeObjectURL(url);
|
||||
}
|
||||
}, [file]);
|
||||
|
||||
return (
|
||||
<Flex gap="4" {...containerProps}>
|
||||
<Heading size="lg" mb="2">
|
||||
Add a profile image
|
||||
</Heading>
|
||||
<VisuallyHiddenInput
|
||||
type="file"
|
||||
accept="image/*"
|
||||
ref={uploadRef}
|
||||
onChange={(e) => setFile(e.target.files?.[0])}
|
||||
/>
|
||||
<Avatar
|
||||
as="button"
|
||||
size="xl"
|
||||
src={preview}
|
||||
onClick={() => uploadRef.current?.click()}
|
||||
cursor="pointer"
|
||||
icon={<ImagePlus boxSize={8} />}
|
||||
/>
|
||||
<Heading size="md">{displayName}</Heading>
|
||||
<Button w="full" colorScheme="primary" mb="4" maxW="sm" onClick={() => onSubmit(file)}>
|
||||
{file ? "Next" : "Skip for now"}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
function RelayButton({ url, selected, onClick }: { url: string; selected: boolean; onClick: () => void }) {
|
||||
const { info } = useRelayInfo(url);
|
||||
|
||||
return (
|
||||
<Card
|
||||
variant="outline"
|
||||
size="sm"
|
||||
borderColor={selected ? "primary.500" : "gray.500"}
|
||||
borderRadius="lg"
|
||||
cursor="pointer"
|
||||
onClick={onClick}
|
||||
>
|
||||
<CardBody>
|
||||
<Flex gap="2" mb="2">
|
||||
<RelayFavicon relay={url} />
|
||||
<Box>
|
||||
<Heading size="sm">{info?.name}</Heading>
|
||||
<Text fontSize="sm">{url}</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Text>{info?.description}</Text>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const recommendedRelays = [
|
||||
"wss://relay.damus.io",
|
||||
"wss://welcome.nostr.wine",
|
||||
"wss://nos.lol",
|
||||
"wss://purplerelay.com",
|
||||
"wss://nostr.bitcoiner.social",
|
||||
"wss://nostr-pub.wellorder.net",
|
||||
];
|
||||
const defaultRelaySelection = new Set(["wss://relay.damus.io", "wss://nos.lol", "wss://welcome.nostr.wine"]);
|
||||
function RelayStep({ onSubmit }: { onSubmit: (relays: string[]) => void }) {
|
||||
const [relays, relayActions] = useSet<string>(defaultRelaySelection);
|
||||
|
||||
return (
|
||||
<Flex gap="4" {...containerProps} maxW="8in">
|
||||
<Heading size="lg" mb="2">
|
||||
Select some relays
|
||||
</Heading>
|
||||
|
||||
<SimpleGrid columns={[1, 1, 2]} spacing="4">
|
||||
{recommendedRelays.map((url) => (
|
||||
<RelayButton key={url} url={url} selected={relays.has(url)} onClick={() => relayActions.toggle(url)} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
{relays.size === 0 && <Text color="orange">You must select at least one relay</Text>}
|
||||
<Button
|
||||
w="full"
|
||||
colorScheme="primary"
|
||||
mb="4"
|
||||
maxW="sm"
|
||||
isDisabled={relays.size === 0}
|
||||
onClick={() => onSubmit(Array.from(relays))}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SignupView() {
|
||||
const [step, setStep] = useState(0);
|
||||
const [metadata, setMetadata] = useState<Kind0ParsedContent>({});
|
||||
const [profileImage, setProfileImage] = useState<File>();
|
||||
const [relays, setRelays] = useState<string[]>([]);
|
||||
|
||||
const renderStep = () => {
|
||||
const next = () => setStep((v) => v + 1);
|
||||
switch (step) {
|
||||
case 0:
|
||||
return (
|
||||
<NameStep
|
||||
onSubmit={(m) => {
|
||||
setMetadata((v) => ({ ...v, ...m }));
|
||||
next();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 1:
|
||||
return (
|
||||
<ProfileImageStep
|
||||
displayName={metadata.display_name}
|
||||
onSubmit={(file) => {
|
||||
setProfileImage(file);
|
||||
next();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<RelayStep
|
||||
onSubmit={(r) => {
|
||||
setRelays(r);
|
||||
next();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Center w="full" h="full">
|
||||
{renderStep()}
|
||||
</Center>
|
||||
);
|
||||
}
|
@ -7,8 +7,8 @@ process.env.VITE_ANALYTICS_SCRIPT = isProd
|
||||
? `
|
||||
<script
|
||||
async defer
|
||||
src="https://ackee.nostrudel.ninja/tracker.js"
|
||||
data-ackee-server="https://ackee.nostrudel.ninja"
|
||||
src="//ackee.nostrudel.ninja/tracker.js"
|
||||
data-ackee-server="//ackee.nostrudel.ninja"
|
||||
data-ackee-domain-id="58b1c39f-43f9-422b-bc7d-06aff35e764e"
|
||||
></script>`
|
||||
: "";
|
||||
|
Loading…
x
Reference in New Issue
Block a user