Add cypress tests

This commit is contained in:
Jonathan Staab 2023-10-12 16:16:38 -07:00
parent 744f42a65f
commit bc88d341d4
29 changed files with 349 additions and 222 deletions

7
cypress.config.ts Normal file
View File

@ -0,0 +1,7 @@
import {defineConfig} from "cypress"
export default defineConfig({
e2e: {
baseUrl: "http://localhost:5173",
},
})

21
cypress/e2e/feed.cy.ts Normal file
View File

@ -0,0 +1,21 @@
describe("feed interaction", () => {
beforeEach(() => {
cy.visit("/")
})
it("clicking a card works", () => {
cy.get(".cy-note-click-target:first").click()
cy.get(".modal .fa-heart")
cy.get(".modal .cy-modal-close").click()
cy.get(".modal").should("not.exist")
})
it("feed controls works", () => {
cy.get(".fa-sliders").click()
cy.get(".modal .cy-chip:first .fa-times").click()
cy.get(".modal").contains("Apply Filters").click()
cy.contains("Kinds 30023,")
cy.get(".card", {timeout: 30000})
})
})

12
cypress/e2e/login.cy.ts Normal file
View File

@ -0,0 +1,12 @@
describe("authenticated usage", () => {
beforeEach(() => {
cy.login()
})
it("works", () => {
cy.visit("/")
cy.get("svg.logo").click()
cy.get(".card").contains("Profile").click()
cy.get(".cy-person-name").contains("test account 12938740")
})
})

10
cypress/e2e/search.cy.ts Normal file
View File

@ -0,0 +1,10 @@
describe("search", () => {
it("works", () => {
cy.visit("/")
cy.get(".cy-top-nav .fa-search").click()
cy.get(".cy-top-nav input").type("hodlbod")
cy.get(".modal").contains("hodlbod").click()
cy.get(".modal").contains("following")
cy.get(".modal").contains("followers")
})
})

23
cypress/e2e/signup.cy.ts Normal file
View File

@ -0,0 +1,23 @@
describe("signup", () => {
beforeEach(() => {
cy.visit("/")
})
it("works", () => {
cy.get(".cy-top-nav").contains("Log In").click()
cy.get(".modal").contains("Sign Up").click()
cy.get(".modal").contains("Let's go!").click()
cy.get(".modal [name=name]").type("9sd2j3e0sd")
cy.get(".modal").contains("Continue").click()
cy.get(".modal").contains("Got it").click()
cy.get(".modal").contains("Continue").click()
cy.get(".modal").contains("Continue").click()
cy.wait(1000)
cy.get(".modal").contains("Skip and see your feed").click()
cy.get(".modal").should("not.exist")
cy.contains("From follows")
cy.get("svg.logo").click()
cy.get(".card").contains("Profile").click()
cy.get(".cy-person-name").contains("9sd2j3e0sd")
})
})

View File

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@ -0,0 +1,29 @@
const nsec = "nsec1er8narhat3xjypf46pksxlr6k4jrmv2e9psazjk0adynly0l4ltsepvmy5"
Cypress.Commands.add("login", () => {
cy.session(
nsec,
() => {
cy.visit("/login/privkey")
cy.get("input[type=password]").type(nsec, {log: false})
cy.get(".cy-login-submit").click()
cy.get(".modal", {timeout: 10_000}).should("not.exist")
cy.contains("Don't have an account?").should("not.exist")
},
{
validate: () => {
let pubkey
cy.window()
.then(w => {
pubkey = w.pubkey.get()
})
.then(() => {
expect(pubkey).to.equal(
"c853d879b7376dab1cdcd4faf235a05f680aae42ba620abdd95d619542a5a379"
)
})
},
}
)
})

20
cypress/support/e2e.ts Normal file
View File

@ -0,0 +1,20 @@
// ***********************************************************
// 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')

View File

@ -23,6 +23,7 @@
"@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0", "@typescript-eslint/parser": "^5.50.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"cypress": "^13.3.1",
"eslint": "^8.33.0", "eslint": "^8.33.0",
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"postcss": "^8.4.19", "postcss": "^8.4.19",
@ -55,6 +56,7 @@
"insane": "^2.6.2", "insane": "^2.6.2",
"lru-cache": "^7.18.3", "lru-cache": "^7.18.3",
"marked": "^5.1.0", "marked": "^5.1.0",
"normalize-url": "^8.0.0",
"nostr-tools": "^1.12.1", "nostr-tools": "^1.12.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"paravel": "^0.3.7", "paravel": "^0.3.7",

View File

@ -2,7 +2,7 @@
import "@fortawesome/fontawesome-free/css/fontawesome.css" import "@fortawesome/fontawesome-free/css/fontawesome.css"
import "@fortawesome/fontawesome-free/css/solid.css" import "@fortawesome/fontawesome-free/css/solid.css"
import {nip19} from "nostr-tools" import {nip19, getPublicKey, generatePrivateKey} from "nostr-tools"
import {pluck} from "ramda" import {pluck} from "ramda"
import {seconds, Fetch} from "hurdak" import {seconds, Fetch} from "hurdak"
import {tryFetch, hexToBech32, bech32ToHex, now} from "src/util/misc" import {tryFetch, hexToBech32, bech32ToHex, now} from "src/util/misc"
@ -223,7 +223,15 @@
// Globals // Globals
Object.assign(window, {...engine, nip19, bech32ToHex, hexToBech32, router}) Object.assign(window, {
...engine,
nip19,
bech32ToHex,
hexToBech32,
router,
getPublicKey,
generatePrivateKey,
})
// Theme // Theme

View File

@ -156,8 +156,8 @@
}} /> }} />
<div <div
class="fixed top-0 z-10 flex h-16 w-full items-center justify-between border-b class="cy-top-nav fixed top-0 z-10 flex h-16 w-full items-center justify-between
border-gray-6 bg-gray-7 px-2 text-gray-2"> border-b border-gray-6 bg-gray-7 px-2 text-gray-2">
<div> <div>
<div class="app-logo flex cursor-pointer items-center gap-2" on:click={toggleMenu}> <div class="app-logo flex cursor-pointer items-center gap-2" on:click={toggleMenu}>
<!-- <img alt="App Logo" src={logoUrl} class="w-10" /> --> <!-- <img alt="App Logo" src={logoUrl} class="w-10" /> -->
@ -183,7 +183,7 @@
<div <div
class={cx( class={cx(
"search-input pointer-events-none fixed top-0 z-10 w-full px-2 text-gray-1", "search-input pointer-events-none fixed top-0 z-10 w-full px-2 text-gray-1",
"flex h-16 items-center justify-end gap-4", "cy-top-nav flex h-16 items-center justify-end gap-4",
{ {
"pr-16": $session, "pr-16": $session,
"pr-28": !$session, "pr-28": !$session,

View File

@ -223,7 +223,7 @@
</div> </div>
{#if muted} {#if muted}
<p class="border-l-2 border-solid border-gray-6 pl-4 text-gray-1"> <p class="border-l-2 border-solid border-gray-6 pl-4 text-gray-1">
You have muted this note. You have hidden this note.
<Anchor <Anchor
theme="anchor" theme="anchor"
on:click={() => { on:click={() => {
@ -233,10 +233,12 @@
{:else} {:else}
<NoteContent {anchorId} note={event} {showEntire} /> <NoteContent {anchorId} note={event} {showEntire} />
{/if} {/if}
<div class="cy-note-click-target h-px" />
<NoteActions <NoteActions
note={event} note={event}
bind:this={actions} bind:this={actions}
{removeFromContext} {removeFromContext}
{showMuted}
{replies} {replies}
{likes} {likes}
{zaps} {zaps}

View File

@ -37,6 +37,7 @@
export let note: Event export let note: Event
export let reply export let reply
export let showMuted
export let showEntire export let showEntire
export let removeFromContext export let removeFromContext
export let replies export let replies
@ -102,7 +103,7 @@
let showDetails = false let showDetails = false
let actions = [] let actions = []
$: disableActions = !$canSign || $muted $: disableActions = !$canSign || ($muted && !showMuted)
$: like = like || find(propEq("pubkey", $session?.pubkey), likes) $: like = like || find(propEq("pubkey", $session?.pubkey), likes)
$: allLikes = like ? likes.filter(n => n.id !== like?.id).concat(like) : likes $: allLikes = like ? likes.filter(n => n.id !== like?.id).concat(like) : likes
$: $likesCount = allLikes.length $: $likesCount = allLikes.length

View File

@ -79,7 +79,7 @@
{:else if quote} {:else if quote}
{#if muted} {#if muted}
<p class="mb-1 py-24 text-center text-gray-5"> <p class="mb-1 py-24 text-center text-gray-5">
You have muted this note. You have hidden this note.
<Anchor class="underline" on:click={unmute}>Show</Anchor> <Anchor class="underline" on:click={unmute}>Show</Anchor>
</p> </p>
{:else} {:else}

View File

@ -30,7 +30,7 @@
</script> </script>
<div class={cx("flex items-center gap-1", $$props.class)}> <div class={cx("flex items-center gap-1", $$props.class)}>
<span>{displayPerson($person)}</span> <span class="cy-person-name">{displayPerson($person)}</span>
<div class="flex items-center gap-1 font-normal"> <div class="flex items-center gap-1 font-normal">
{#if $following} {#if $following}
<span class="px-2 py-1 text-xs"> <span class="px-2 py-1 text-xs">

View File

@ -15,13 +15,13 @@
loginWithExtension(await ext.getPublicKey()) loginWithExtension(await ext.getPublicKey())
boot() boot()
} else { } else {
router.at("login/privkey").open() router.at("login/privkey").replaceModal()
} }
}) })
const signUp = () => router.at("onboarding").open() const signUp = () => router.at("onboarding").replaceModal()
const advancedLogIn = () => router.at("login/advanced").open() const advancedLogIn = () => router.at("login/advanced").replaceModal()
document.title = "Log In" document.title = "Log In"
</script> </script>

View File

@ -131,13 +131,15 @@
<Spinner /> <Spinner />
{:else if Object.values(currentRelays).length > 0} {:else if Object.values(currentRelays).length > 0}
<p>Currently searching:</p> <p>Currently searching:</p>
{#each Object.values(currentRelays) as relay} <Content gap="gap-4" size="inherit">
<div class="h-12"> {#each Object.values(currentRelays) as relay}
{#if relay} <div class="h-12">
<RelayCard hideActions relay={{...relay, description: null}} /> {#if relay}
{/if} <RelayCard hideActions relay={{...relay, description: null}} />
</div> {/if}
{/each} </div>
{/each}
</Content>
{/if} {/if}
</Content> </Content>

View File

@ -33,7 +33,7 @@
<i slot="before" class="fa fa-key" /> <i slot="before" class="fa fa-key" />
</Input> </Input>
</div> </div>
<Anchor theme="button" on:click={logIn}>Log In</Anchor> <Anchor theme="button" class="cy-login-submit" on:click={logIn}>Log In</Anchor>
</div> </div>
{#if !Capacitor.isNativePlatform()} {#if !Capacitor.isNativePlatform()}
<p class="rounded border-2 border-solid border-warning bg-gray-8 px-6 py-3"> <p class="rounded border-2 border-solid border-warning bg-gray-8 px-6 py-3">

View File

@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {generatePrivateKey} from "nostr-tools" import {generatePrivateKey} from "nostr-tools"
import {closure} from "hurdak" import {closure, sleep} from "hurdak"
import {fly} from "src/util/transition" import {fly} from "src/util/transition"
import Content from "src/partials/Content.svelte"
import OnboardingIntro from "src/app/views/OnboardingIntro.svelte" import OnboardingIntro from "src/app/views/OnboardingIntro.svelte"
import OnboardingProfile from "src/app/views/OnboardingProfile.svelte" import OnboardingProfile from "src/app/views/OnboardingProfile.svelte"
import OnboardingKey from "src/app/views/OnboardingKey.svelte" import OnboardingKey from "src/app/views/OnboardingKey.svelte"
@ -62,6 +63,13 @@
}) })
const signup = async noteContent => { const signup = async noteContent => {
// Go to our home page
router.at("notes").push()
// Make things async since the `key` change in App.svelte prevents the modal
// animation from completing, and it gets stuck. This is a svelte bug
await sleep(10)
loginWithPrivateKey(privkey) loginWithPrivateKey(privkey)
// Do this first so we know where to publish everything else // Do this first so we know where to publish everything else
@ -78,12 +86,6 @@
// Start our notifications listener // Start our notifications listener
listenForNotifications() listenForNotifications()
// Close all modals
router.clearModals()
// Go to our home page
router.at("notes").push()
} }
onMount(() => { onMount(() => {
@ -94,18 +96,20 @@
{#key stage} {#key stage}
<div in:fly={{y: 20}}> <div in:fly={{y: 20}}>
{#if stage === "intro"} <Content size="lg">
<OnboardingIntro {setStage} /> {#if stage === "intro"}
{:else if stage === "profile"} <OnboardingIntro {setStage} />
<OnboardingProfile {setStage} {profile} /> {:else if stage === "profile"}
{:else if stage === "key"} <OnboardingProfile {setStage} {profile} />
<OnboardingKey {setStage} {privkey} /> {:else if stage === "key"}
{:else if stage === "relays"} <OnboardingKey {setStage} {privkey} />
<OnboardingRelays {setStage} bind:relays /> {:else if stage === "relays"}
{:else if stage === "follows"} <OnboardingRelays {setStage} bind:relays />
<OnboardingFollows {setStage} bind:petnames /> {:else if stage === "follows"}
{:else if stage === "note"} <OnboardingFollows {setStage} bind:petnames />
<OnboardingNote {setStage} {signup} /> {:else if stage === "note"}
{/if} <OnboardingNote {setStage} {signup} />
{/if}
</Content>
</div> </div>
{/key} {/key}

View File

@ -3,7 +3,6 @@
import Input from "src/partials/Input.svelte" import Input from "src/partials/Input.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte" import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
import PersonSummary from "src/app/shared/PersonSummary.svelte" import PersonSummary from "src/app/shared/PersonSummary.svelte"
import type {Person} from "src/engine" import type {Person} from "src/engine"
import {env, mention, loadPeople, searchPeople} from "src/engine" import {env, mention, loadPeople, searchPeople} from "src/engine"
@ -29,58 +28,54 @@
$: results = reject((p: Person) => pubkeys.includes(p.pubkey), $searchPeople(q)) $: results = reject((p: Person) => pubkeys.includes(p.pubkey), $searchPeople(q))
</script> </script>
<Content> <Heading class="text-center">Find Your People</Heading>
<Content class="text-center"> <p>
<Heading>Find Your People</Heading> To get you started, weve added some interesting people to your follow list. You can update your
<p> follows list at any time.
To get you started, weve added some interesting people to your follow list. You can update </p>
your follows list at any time. <div class="flex gap-2">
</p> <Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<div class="flex gap-2"> <Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor>
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor> </div>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor> <div class="flex items-center gap-2">
</div> <i class="fa fa-user-astronaut fa-lg" />
</Content> <h2 class="staatliches text-2xl">Your follows</h2>
<div class="flex items-center gap-2"> </div>
<i class="fa fa-user-astronaut fa-lg" /> {#if pubkeys.length === 0}
<h2 class="staatliches text-2xl">Your follows</h2> <div class="mt-8 flex items-center justify-center gap-2 text-center">
<i class="fa fa-triangle-exclamation" />
<span>No follows selected</span>
</div> </div>
{#if pubkeys.length === 0} {:else}
<div class="mt-8 flex items-center justify-center gap-2 text-center"> {#each pubkeys as pubkey (pubkey)}
<i class="fa fa-triangle-exclamation" /> <PersonSummary {pubkey}>
<span>No follows selected</span>
</div>
{:else}
{#each pubkeys as pubkey (pubkey)}
<PersonSummary {pubkey}>
<div slot="actions" class="flex items-start justify-end">
<Anchor
theme="button"
class="flex items-center gap-2"
on:click={() => removeFollow(pubkey)}>
<i class="fa fa-user-slash" /> Unfollow
</Anchor>
</div>
</PersonSummary>
{/each}
{/if}
<div class="flex items-center gap-2">
<i class="fa fa-earth-asia fa-lg" />
<h2 class="staatliches text-2xl">Other people</h2>
</div>
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
<i slot="before" class="fa-solid fa-search" />
</Input>
{#each results.slice(0, 50) as profile (profile.pubkey)}
<PersonSummary pubkey={profile.pubkey}>
<div slot="actions" class="flex items-start justify-end"> <div slot="actions" class="flex items-start justify-end">
<Anchor <Anchor
theme="button-accent" theme="button"
class="flex items-center gap-2" class="flex items-center gap-2"
on:click={() => addFollow(profile.pubkey)}> on:click={() => removeFollow(pubkey)}>
<i class="fa fa-user-plus" /> Follow <i class="fa fa-user-slash" /> Unfollow
</Anchor> </Anchor>
</div> </div>
</PersonSummary> </PersonSummary>
{/each} {/each}
</Content> {/if}
<div class="flex items-center gap-2">
<i class="fa fa-earth-asia fa-lg" />
<h2 class="staatliches text-2xl">Other people</h2>
</div>
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
<i slot="before" class="fa-solid fa-search" />
</Input>
{#each results.slice(0, 50) as profile (profile.pubkey)}
<PersonSummary pubkey={profile.pubkey}>
<div slot="actions" class="flex items-start justify-end">
<Anchor
theme="button-accent"
class="flex items-center gap-2"
on:click={() => addFollow(profile.pubkey)}>
<i class="fa fa-user-plus" /> Follow
</Anchor>
</div>
</PersonSummary>
{/each}

View File

@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte" import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
export let setStage export let setStage
@ -10,16 +9,14 @@
const next = () => setStage("profile") const next = () => setStage("profile")
</script> </script>
<Content size="lg" class="text-center"> <Heading class="text-center">Create an Account</Heading>
<Heading>Create an Account</Heading> <p>
<p> New to Nostr? Click <Anchor class="underline" external href={tutorialUrl}>here</Anchor> or watch the
New to Nostr? Click <Anchor class="underline" external href={tutorialUrl}>here</Anchor> or watch video below for a crash course on what it is, and how to use it.
the video below for a crash course on what it is, and how to use it. </p>
</p> <video controls src={videoUrl} class="object-contain object-center" />
<video controls src={videoUrl} class="object-contain object-center" /> <p>
<p> When youre ready to dive in, click below and well guide you through the process of creating an
When youre ready to dive in, click below and well guide you through the process of creating an account.
account. </p>
</p> <Anchor theme="button-accent" class="text-center" on:click={next}>Let's go!</Anchor>
<Anchor theme="button-accent" on:click={next}>Let's go!</Anchor>
</Content>

View File

@ -5,7 +5,6 @@
import Input from "src/partials/Input.svelte" import Input from "src/partials/Input.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte" import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
import {env} from "src/engine" import {env} from "src/engine"
export let privkey export let privkey
@ -21,19 +20,17 @@
} }
</script> </script>
<Content size="lg" class="text-center"> <Heading class="text-center">Generate a Key</Heading>
<Heading>Generate a Key</Heading> <p>
<p> Your private key is your password, and gives you total control over your Nostr account. We've
Your private key is your password, and gives you total control over your Nostr account. We've generated a fresh one for you below store it somewhere safe!
generated a fresh one for you below store it somewhere safe! </p>
</p> <Input disabled placeholder={"•".repeat(53)} wrapperClass="flex-grow">
<Input disabled placeholder={"•".repeat(53)} wrapperClass="flex-grow"> <i slot="before" class="fa fa-lock" />
<i slot="before" class="fa fa-lock" /> <button slot="after" class="fa fa-copy cursor-pointer" on:click={copyKey} />
<button slot="after" class="fa fa-copy cursor-pointer" on:click={copyKey} /> </Input>
</Input> <p>If you don't want to save your keys now, you can find them later in {appName}'s settings.</p>
<p>If you don't want to save your keys now, you can find them later in {appName}'s settings.</p> <div class="flex gap-2">
<div class="flex gap-2"> <Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor> <Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Got it</Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Got it</Anchor> </div>
</div>
</Content>

View File

@ -3,7 +3,6 @@
import Compose from "src/app/shared/Compose.svelte" import Compose from "src/app/shared/Compose.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte" import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
export let signup export let signup
export let setStage export let setStage
@ -19,20 +18,18 @@
}) })
</script> </script>
<Content size="lg"> <Heading class="text-center">Welcome to Nostr</Heading>
<Heading class="text-center">Welcome to Nostr</Heading> <p>
<p class="text-center"> Your're all set! If have any questions, just ask! People around these parts are always ready to
Your're all set! If have any questions, just ask! People around these parts are always ready to lend a hand.
lend a hand. </p>
</p> <div class="border-l-2 border-solid border-gray-6 pl-4">
<div class="border-l-2 border-solid border-gray-6 pl-4"> <Compose bind:this={compose} onSubmit={next} />
<Compose bind:this={compose} onSubmit={next} /> </div>
</div> <div class="flex gap-2">
<div class="flex gap-2"> <Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor> <Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Say Hello!</Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Say Hello!</Anchor> </div>
</div> <Anchor class="text-center" on:click={skip}>
<Anchor class="text-center" on:click={skip}> Skip and see your feed <i class="fa fa-arrow-right" />
Skip and see your feed <i class="fa fa-arrow-right" /> </Anchor>
</Anchor>
</Content>

View File

@ -4,7 +4,6 @@
import ImageInput from "src/partials/ImageInput.svelte" import ImageInput from "src/partials/ImageInput.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte" import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
export let profile export let profile
export let setStage export let setStage
@ -13,35 +12,28 @@
const next = () => setStage("key") const next = () => setStage("key")
</script> </script>
<Content size="lg"> <Heading class="text-center">Introduce Yourself</Heading>
<Heading class="text-center">Introduce Yourself</Heading> <p>
<p class="text-center"> Give other people something to go on. Remember that "privacy is the power to selectively reveal
Give other people something to go on. Remember that "privacy is the power to selectively reveal oneself to the world".
oneself to the world". </p>
</p> <div class="flex flex-col gap-2">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-1">
<div class="flex flex-col gap-1"> <strong>Your Name</strong>
<strong>Your Name</strong> <Input type="text" name="name" wrapperClass="flex-grow" bind:value={profile.name}>
<Input type="text" name="name" wrapperClass="flex-grow" bind:value={profile.name}> <i slot="before" class="fa-solid fa-user-astronaut" />
<i slot="before" class="fa-solid fa-user-astronaut" /> </Input>
</Input>
</div>
<div class="flex flex-col gap-1">
<strong>About You</strong>
<Textarea name="about" bind:value={profile.about} />
</div>
<div class="flex flex-col gap-1">
<strong>Profile Picture</strong>
<ImageInput
bind:value={profile.picture}
icon="image-portrait"
maxWidth={480}
maxHeight={480} />
<p class="text-sm text-gray-1">Please be mindful of others and only use small images.</p>
</div>
</div> </div>
<div class="flex gap-2"> <div class="flex flex-col gap-1">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor> <strong>About You</strong>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor> <Textarea name="about" bind:value={profile.about} />
</div> </div>
</Content> <div class="flex flex-col gap-1">
<strong>Profile Picture</strong>
<ImageInput bind:value={profile.picture} icon="image-portrait" maxWidth={480} maxHeight={480} />
</div>
</div>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor>
</div>

View File

@ -34,52 +34,50 @@
) )
</script> </script>
<Content> <Heading class="text-center">Get Connected</Heading>
<div class="text-center"> <p>
<Heading>Get Connected</Heading> Nostr is a protocol, not a platform. This means that <i>you</i> choose where to store your data.
<p> </p>
Nostr is a protocol, not a platform. This means that <i>you</i> choose where to store your data. <p>
Select your preferred storage relays below, or click "continue" to use some reasonable defaults. Select your preferred storage relays below, or click "continue" to use some reasonable defaults.
You can change your selection any time. You can change your selection any time.
</p> </p>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor>
</div>
<div class="flex items-center gap-2">
<i class="fa fa-server fa-lg" />
<h2 class="staatliches text-2xl">Your relays</h2>
</div>
{#if relays.length === 0}
<div class="mt-8 flex items-center justify-center gap-2 text-center">
<i class="fa fa-triangle-exclamation" />
<span>No relays connected</span>
</div> </div>
<div class="flex gap-2"> {:else}
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor> <Content gap="gap-4" size="inherit">
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor> {#each relays as relay (relay.url)}
</div> <RelayCard {relay}>
<div class="flex items-center gap-2"> <div slot="actions">
<i class="fa fa-server fa-lg" /> {#if relays.length > 1}
<h2 class="staatliches text-2xl">Your relays</h2> <button class="flex items-center gap-3 text-gray-1" on:click={() => removeRelay(relay)}>
</div> <i class="fa fa-right-from-bracket" /> Leave
{#if relays.length === 0} </button>
<div class="mt-8 flex items-center justify-center gap-2 text-center"> {/if}
<i class="fa fa-triangle-exclamation" /> </div>
<span>No relays connected</span> </RelayCard>
</div> {/each}
{:else} </Content>
<div class="grid grid-cols-1 gap-4"> {/if}
{#each relays as relay (relay.url)} <div class="flex items-center gap-2">
<RelayCard {relay}> <i class="fa fa-earth-asia fa-lg" />
<div slot="actions"> <h2 class="staatliches text-2xl">Other relays</h2>
{#if relays.length > 1} </div>
<button <Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
class="flex items-center gap-3 text-gray-1" <i slot="before" class="fa-solid fa-search" />
on:click={() => removeRelay(relay)}> </Input>
<i class="fa fa-right-from-bracket" /> Leave <Content gap="gap-4" size="inherit">
</button>
{/if}
</div>
</RelayCard>
{/each}
</div>
{/if}
<div class="flex items-center gap-2">
<i class="fa fa-earth-asia fa-lg" />
<h2 class="staatliches text-2xl">Other relays</h2>
</div>
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
<i slot="before" class="fa-solid fa-search" />
</Input>
{#each (search(q) || []).slice(0, 50) as relay (relay.url)} {#each (search(q) || []).slice(0, 50) as relay (relay.url)}
<RelayCard {relay}> <RelayCard {relay}>
<div slot="actions"> <div slot="actions">
@ -89,8 +87,8 @@
</div> </div>
</RelayCard> </RelayCard>
{/each} {/each}
<small class="text-center">
Showing {Math.min($knownRelays.length - relays.length, 50)}
of {$knownRelays.length - relays.length} known relays
</small>
</Content> </Content>
<small class="text-center">
Showing {Math.min($knownRelays.length - relays.length, 50)}
of {$knownRelays.length - relays.length} known relays
</small>

View File

@ -4,10 +4,14 @@
export let theme = "dark" export let theme = "dark"
export let onRemove = null export let onRemove = null
const className = cx($$props.class, "inline-block rounded-full border border-solid py-1 px-2", { const className = cx(
"border-gray-1": theme === "dark", $$props.class,
"border-gray-8": theme === "light", "inline-block rounded-full border border-solid py-1 px-2 cy-chip",
}) {
"border-gray-1": theme === "dark",
"border-gray-8": theme === "light",
}
)
</script> </script>
<div class={className}> <div class={className}>

View File

@ -66,7 +66,7 @@
<div <div
class="pointer-events-auto flex h-10 w-10 cursor-pointer items-center justify-center rounded-full class="pointer-events-auto flex h-10 w-10 cursor-pointer items-center justify-center rounded-full
border border-solid border-accent-light bg-accent text-white transition-colors hover:bg-accent-light"> border border-solid border-accent-light bg-accent text-white transition-colors hover:bg-accent-light">
<i class="fa fa-times fa-lg" /> <i class="fa fa-times fa-lg cy-modal-close" />
</div> </div>
{#if $modals.length > 1 && index > 0} {#if $modals.length > 1 && index > 0}
<div <div

View File

@ -3,6 +3,7 @@
"include": ["src/**/*"], "include": ["src/**/*"],
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"module": "esnext",
"paths": { "paths": {
"src/*": ["src/*"] "src/*": ["src/*"]
}, },

BIN
yarn.lock

Binary file not shown.