mirror of
https://github.com/acid-info/Kurate.git
synced 2025-02-20 19:48:16 +00:00
feat: added personas while still maintaining the ability to post (#154)
This commit is contained in:
parent
e24ab63af1
commit
9855038cac
@ -1,42 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
let cls: string | undefined = undefined
|
|
||||||
export { cls as class }
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class={`root header-description ${cls}`}>
|
|
||||||
<div class="subtitle">Public timeline</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.root {
|
|
||||||
padding: var(--spacing-12);
|
|
||||||
transition: padding 0.2s, margin 0.2s;
|
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
padding-bottom: var(--spacing-12);
|
|
||||||
margin: 0;
|
|
||||||
transition: padding 0.2s, margin 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
&.scrolled {
|
|
||||||
box-shadow: 0 1px 5px 0 rgba(var(--color-body-bg-rgb), 0.75);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
padding: 0;
|
|
||||||
width: 1280px;
|
|
||||||
margin: 0 auto 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,21 +1,28 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import UserIcon from '$lib/components/icons/user.svelte'
|
|
||||||
import Button from './button.svelte'
|
import Button from './button.svelte'
|
||||||
|
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import { ROUTES } from '$lib/routes'
|
import { ROUTES } from '$lib/routes'
|
||||||
import Wallet from './icons/wallet.svelte'
|
import Wallet from './icons/wallet.svelte'
|
||||||
import Edit from './icons/edit.svelte'
|
import { formatAddress } from '$lib/utils'
|
||||||
import UpToTop from './icons/up-to-top.svelte'
|
import { profile } from '$lib/stores/profile'
|
||||||
|
import { connectWallet } from '$lib/services'
|
||||||
|
|
||||||
let cls: string | undefined = undefined
|
let cls: string | undefined = undefined
|
||||||
export { cls as class }
|
export { cls as class }
|
||||||
|
|
||||||
let y: number
|
let y: number
|
||||||
export let loggedin: boolean | undefined = undefined
|
export let address: string | undefined = undefined
|
||||||
|
|
||||||
function goTop() {
|
const handleConnect = async () => {
|
||||||
document.body.scrollIntoView()
|
try {
|
||||||
|
const signer = await connectWallet()
|
||||||
|
const address = await signer.getAddress()
|
||||||
|
|
||||||
|
$profile = { signer, address }
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -25,26 +32,25 @@
|
|||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="header-title-wrap">
|
<div class="header-title-wrap">
|
||||||
<div class="top-button">
|
<span class={`header-title ${cls}`}>Kurate</span>
|
||||||
<Button icon={UpToTop} on:click={goTop} />
|
|
||||||
</div>
|
|
||||||
<span class={` ${y > 0 ? 'subtitle' : 'header-title'} ${cls}`}>
|
|
||||||
{y > 0 ? 'Public Timeline' : 'The Outlet'}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<div class="btn wallet">
|
{#if address}
|
||||||
<Button
|
<Button
|
||||||
icon={loggedin ? Edit : Wallet}
|
icon={Wallet}
|
||||||
variant="primary"
|
variant={'secondary'}
|
||||||
on:click={() => goto(loggedin ? ROUTES.POST_NEW : ROUTES.PROFILE)}
|
label={formatAddress(address)}
|
||||||
|
on:click={() => goto(ROUTES.PROFILE)}
|
||||||
/>
|
/>
|
||||||
</div>
|
{:else}
|
||||||
|
<Button
|
||||||
<div class="btn user">
|
icon={Wallet}
|
||||||
<Button icon={UserIcon} variant="secondary" on:click={() => goto(ROUTES.PROFILE)} />
|
variant={'primary'}
|
||||||
</div>
|
label={'Connect'}
|
||||||
|
on:click={() => handleConnect()}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -122,13 +128,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-12);
|
gap: var(--spacing-12);
|
||||||
margin-left: -56px;
|
|
||||||
transition: margin 0.2s ease-in-out;
|
transition: margin 0.2s ease-in-out;
|
||||||
|
|
||||||
.top-button {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.scrolled {
|
&.scrolled {
|
||||||
|
@ -1,228 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import UserIcon from '$lib/components/icons/user.svelte'
|
|
||||||
import Button from './button.svelte'
|
|
||||||
|
|
||||||
import { goto } from '$app/navigation'
|
|
||||||
import { ROUTES } from '$lib/routes'
|
|
||||||
import Wallet from './icons/wallet.svelte'
|
|
||||||
import Edit from './icons/edit.svelte'
|
|
||||||
import UpToTop from './icons/up-to-top.svelte'
|
|
||||||
|
|
||||||
let cls: string | undefined = undefined
|
|
||||||
export { cls as class }
|
|
||||||
|
|
||||||
let y: number
|
|
||||||
export let loggedin: boolean | undefined = undefined
|
|
||||||
|
|
||||||
function goTop() {
|
|
||||||
document.body.scrollIntoView()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:window bind:scrollY={y} />
|
|
||||||
|
|
||||||
<div class={`root ${y > 0 ? 'scrolled' : ''} ${cls}`}>
|
|
||||||
<div class="header-content">
|
|
||||||
<div class="header">
|
|
||||||
<div class="header-title-wrap">
|
|
||||||
<div class="top-button">
|
|
||||||
<Button icon={UpToTop} on:click={goTop} />
|
|
||||||
</div>
|
|
||||||
<span class={` ${y > 0 ? 'subtitle' : 'header-title'} ${cls}`}>
|
|
||||||
{y > 0 ? 'Public Timeline' : 'The Outlet'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btns">
|
|
||||||
<div class="btn wallet">
|
|
||||||
<Button
|
|
||||||
icon={loggedin ? Edit : Wallet}
|
|
||||||
variant="primary"
|
|
||||||
on:click={() => goto(loggedin ? ROUTES.POST_NEW : ROUTES.PROFILE)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn user">
|
|
||||||
<Button icon={UserIcon} variant="secondary" on:click={() => goto(ROUTES.PROFILE)} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if y === 0}
|
|
||||||
<div class="header-description">
|
|
||||||
Milestone 1 shaman pitchfork typewriter single-origin coffee beard flannel, actually
|
|
||||||
chillwave.
|
|
||||||
</div>
|
|
||||||
<div class={`subtitle ${y > 0 ? 'hide' : ''} ${cls}`}>Public timeline</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.root {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: var(--spacing-12);
|
|
||||||
border-bottom: 1px solid var(--grey-200);
|
|
||||||
background-color: rgba(var(--color-body-bg-rgb), 0.93);
|
|
||||||
backdrop-filter: blur(3px);
|
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
border-bottom: none;
|
|
||||||
padding-bottom: 0;
|
|
||||||
padding-top: var(--spacing-48);
|
|
||||||
transition: padding 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto 0;
|
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
padding-bottom: var(--spacing-12);
|
|
||||||
border-bottom: 1px solid var(--grey-200);
|
|
||||||
transition: padding 0.2s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btns {
|
|
||||||
position: relative;
|
|
||||||
height: 44px;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
&.user {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.wallet {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hide {
|
|
||||||
opacity: 0;
|
|
||||||
font-size: 0;
|
|
||||||
transition: opacity 0.2s, font-size 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 0.2s, font-size 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.scrolled {
|
|
||||||
box-shadow: 0 1px 5px 0 rgba(var(--color-body-text-rgb), 0.25);
|
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
padding: var(--spacing-12);
|
|
||||||
transition: padding 0.2s;
|
|
||||||
|
|
||||||
.header-content {
|
|
||||||
border-bottom: none;
|
|
||||||
padding-bottom: 0;
|
|
||||||
transition: padding 0.2s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.user {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.wallet {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
padding-bottom: 0;
|
|
||||||
transition: padding 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title-wrap {
|
|
||||||
margin-left: 0;
|
|
||||||
transition: margin 0.2s ease-in-out;
|
|
||||||
|
|
||||||
.top-button {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-description,
|
|
||||||
.subtitle.hide {
|
|
||||||
height: 0;
|
|
||||||
opacity: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0;
|
|
||||||
transition: height 0.2s, opacity 0.2s, padding 0.2s, margin 0.2s, font-size 0.2s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
border-bottom-color: var(--grey-500);
|
|
||||||
&.scrolled {
|
|
||||||
box-shadow: 0 1px 5px 0 rgba(var(--color-body-bg-rgb), 0.75);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 0 var(--spacing-24);
|
|
||||||
transition: padding 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-family: var(--font-body);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 18px;
|
|
||||||
font-style: normal;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title-wrap {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-12);
|
|
||||||
margin-left: -56px;
|
|
||||||
transition: margin 0.2s ease-in-out;
|
|
||||||
|
|
||||||
.top-button {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-description {
|
|
||||||
padding: 0 0 var(--spacing-24);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
13
packages/ui/src/lib/components/icons/add.svelte
Normal file
13
packages/ui/src/lib/components/icons/add.svelte
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { IconProps } from '$lib/types'
|
||||||
|
|
||||||
|
type $$Props = IconProps
|
||||||
|
|
||||||
|
let cls: string | undefined = undefined
|
||||||
|
export { cls as class }
|
||||||
|
export let size = 20
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width={size} height={size} class={cls}>
|
||||||
|
<polygon points="17,15 17,8 15,8 15,15 8,15 8,17 15,17 15,24 17,24 17,17 24,17 24,15 " />
|
||||||
|
</svg>
|
15
packages/ui/src/lib/components/icons/search.svelte
Normal file
15
packages/ui/src/lib/components/icons/search.svelte
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { IconProps } from '$lib/types'
|
||||||
|
|
||||||
|
type $$Props = IconProps
|
||||||
|
|
||||||
|
let cls: string | undefined = undefined
|
||||||
|
export { cls as class }
|
||||||
|
export let size = 20
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width={size} height={size} class={cls}>
|
||||||
|
<path
|
||||||
|
d="M29,27.5859l-7.5521-7.5521a11.0177,11.0177,0,1,0-1.4141,1.4141L27.5859,29ZM4,13a9,9,0,1,1,9,9A9.01,9.01,0,0,1,4,13Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { IconProps } from '$lib/types'
|
||||||
|
|
||||||
|
type $$Props = IconProps
|
||||||
|
|
||||||
|
let cls: string | undefined = undefined
|
||||||
|
export { cls as class }
|
||||||
|
export let size = 20
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width={size} height={size} class={cls}>
|
||||||
|
<path
|
||||||
|
d="M27.71,4.29a1,1,0,0,0-1.05-.23l-22,8a1,1,0,0,0,0,1.87l8.59,3.43L19.59,11,21,12.41l-6.37,6.37,3.44,8.59A1,1,0,0,0,19,28h0a1,1,0,0,0,.92-.66l8-22A1,1,0,0,0,27.71,4.29Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
20
packages/ui/src/lib/components/icons/settings-view.svelte
Normal file
20
packages/ui/src/lib/components/icons/settings-view.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { IconProps } from '$lib/types'
|
||||||
|
|
||||||
|
type $$Props = IconProps
|
||||||
|
|
||||||
|
let cls: string | undefined = undefined
|
||||||
|
export { cls as class }
|
||||||
|
export let size = 20
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width={size} height={size} class={cls}>
|
||||||
|
<circle cx="23" cy="23.9999" r="2" />
|
||||||
|
<path
|
||||||
|
d="M30.7769,23.4785A8.64,8.64,0,0,0,23,18a8.64,8.64,0,0,0-7.7769,5.4785L15,24l.2231.5215A8.64,8.64,0,0,0,23,30a8.64,8.64,0,0,0,7.7769-5.4785L31,24ZM23,28a4,4,0,1,1,4-4A4.0045,4.0045,0,0,1,23,28Z"
|
||||||
|
/>
|
||||||
|
<path d="M12.3989,20.8A6,6,0,1,1,22,16H20a4,4,0,1,0-6.4,3.2Z" />
|
||||||
|
<path
|
||||||
|
d="M29.3047,11.0439,26.9441,6.9561a1.9977,1.9977,0,0,0-2.3728-.8946l-2.4341.8233a11.0419,11.0419,0,0,0-1.312-.7583l-.5037-2.5186A2,2,0,0,0,18.36,2H13.64a2,2,0,0,0-1.9611,1.6079l-.5037,2.5186A10.9666,10.9666,0,0,0,9.8481,6.88L7.4287,6.0615a1.9977,1.9977,0,0,0-2.3728.8946L2.6953,11.0439a2.0006,2.0006,0,0,0,.4119,2.5025l1.9309,1.6968C5.021,15.4946,5,15.7446,5,16c0,.2578.01.5127.0278.7656l-1.9206,1.688a2.0006,2.0006,0,0,0-.4119,2.5025l2.3606,4.0878a1.9977,1.9977,0,0,0,2.3728.8946l2.4341-.8233a10.9736,10.9736,0,0,0,1.312.7583l.5037,2.5186A2,2,0,0,0,13.64,30H15V28H13.64l-.71-3.5508a9.0953,9.0953,0,0,1-2.6948-1.5713l-3.4468,1.166-2.36-4.0878L7.1528,17.561a8.9263,8.9263,0,0,1-.007-3.1279L4.4275,12.0439,6.7886,7.9561l3.4267,1.1591a9.0305,9.0305,0,0,1,2.7141-1.5644L13.64,4H18.36l.71,3.5508a9.0978,9.0978,0,0,1,2.6948,1.5713l3.4468-1.166,2.36,4.0878-2.7978,2.4522L26.0923,16l2.8-2.4536A2.0006,2.0006,0,0,0,29.3047,11.0439Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
18
packages/ui/src/lib/components/icons/user-multiple.svelte
Normal file
18
packages/ui/src/lib/components/icons/user-multiple.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { IconProps } from '$lib/types'
|
||||||
|
|
||||||
|
type $$Props = IconProps
|
||||||
|
|
||||||
|
let cls: string | undefined = undefined
|
||||||
|
export { cls as class }
|
||||||
|
export let size = 20
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width={size} height={size} class={cls}>
|
||||||
|
<path d="M30,30H28V25a5.0057,5.0057,0,0,0-5-5V18a7.0078,7.0078,0,0,1,7,7Z" />
|
||||||
|
<path
|
||||||
|
d="M22,30H20V25a5.0059,5.0059,0,0,0-5-5H9a5.0059,5.0059,0,0,0-5,5v5H2V25a7.0082,7.0082,0,0,1,7-7h6a7.0082,7.0082,0,0,1,7,7Z"
|
||||||
|
/>
|
||||||
|
<path d="M20,2V4a5,5,0,0,1,0,10v2A7,7,0,0,0,20,2Z" />
|
||||||
|
<path d="M12,4A5,5,0,1,1,7,9a5,5,0,0,1,5-5m0-2a7,7,0,1,0,7,7A7,7,0,0,0,12,2Z" />
|
||||||
|
</svg>
|
57
packages/ui/src/lib/components/persona.svelte
Normal file
57
packages/ui/src/lib/components/persona.svelte
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import UserMultiple from './icons/user-multiple.svelte'
|
||||||
|
|
||||||
|
let cls: string | undefined = undefined
|
||||||
|
export { cls as class }
|
||||||
|
export let name: string
|
||||||
|
export let description: string
|
||||||
|
export let postsCount: number
|
||||||
|
export let picture: string | undefined
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div class={`root ${cls}`} on:click>
|
||||||
|
<div class="picture"><img src={picture} alt="persona" /></div>
|
||||||
|
<div class="details">
|
||||||
|
<div class="header">{name}</div>
|
||||||
|
<div class="description">{description}</div>
|
||||||
|
<div class="post-count"><UserMultiple size={18} /> {postsCount}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picture {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
flex-basis: 100px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
.post-count {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,59 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { goto } from '$app/navigation'
|
|
||||||
import { ROUTES } from '$lib/routes'
|
|
||||||
import Button from './button.svelte'
|
|
||||||
import WalletIcon from './icons/wallet.svelte'
|
|
||||||
|
|
||||||
let cls: string | undefined = undefined
|
|
||||||
let y: number
|
|
||||||
export { cls as class }
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:window bind:scrollY={y} />
|
|
||||||
<div class={`root ${y > 0 ? 'scrolled' : ''} ${cls}`}>
|
|
||||||
<Button
|
|
||||||
icon={WalletIcon}
|
|
||||||
variant="primary"
|
|
||||||
label="Connect wallet to post"
|
|
||||||
on:click={() => goto(ROUTES.PROFILE)}
|
|
||||||
/>
|
|
||||||
<div class="description">Connect a wallet to access or create your account.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.root {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: var(--spacing-24) var(--spacing-12);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-top: 1px solid var(--grey-200);
|
|
||||||
border-bottom: 1px solid var(--grey-200);
|
|
||||||
transition: height 0.2s, padding 0.2s, margin 0.2s, font-size 0.2s;
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
border: none;
|
|
||||||
outline: 1px solid var(--grey-200);
|
|
||||||
outline-offset: -0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
border-top-color: var(--grey-500);
|
|
||||||
border-left-color: var(--grey-500);
|
|
||||||
border-bottom-color: var(--grey-500);
|
|
||||||
outline-color: var(--grey-500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.description {
|
|
||||||
margin-top: var(--spacing-12);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
font-weight: 400;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,5 +1,7 @@
|
|||||||
export const ROUTES = {
|
export const ROUTES = {
|
||||||
HOME: '/',
|
HOME: '/',
|
||||||
PROFILE: '/profile',
|
PROFILE: '/profile',
|
||||||
POST_NEW: '/post/new',
|
PERSONA: (slug: string) => `/persona/${slug}`,
|
||||||
|
PERSONA_NEW: '/persona/new',
|
||||||
|
POST_NEW: (slug: string) => `/persona/${slug}/post/new`,
|
||||||
}
|
}
|
||||||
|
15
packages/ui/src/lib/stores/chat.ts
Normal file
15
packages/ui/src/lib/stores/chat.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { writable, type Writable } from 'svelte/store'
|
||||||
|
|
||||||
|
interface ChatData {
|
||||||
|
unread: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChatStore = Writable<ChatData>
|
||||||
|
|
||||||
|
function createChatStore(): ChatStore {
|
||||||
|
const store = writable<ChatData>({ unread: 3 })
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
export const chats = createChatStore()
|
86
packages/ui/src/lib/stores/persona.ts
Normal file
86
packages/ui/src/lib/stores/persona.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { writable, type Writable } from 'svelte/store'
|
||||||
|
import type { Identity } from '@semaphore-protocol/identity'
|
||||||
|
|
||||||
|
interface Persona {
|
||||||
|
identity?: Identity
|
||||||
|
picture?: string
|
||||||
|
name: string
|
||||||
|
pitch: string
|
||||||
|
description: string
|
||||||
|
postsCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PersonaStore = Writable<{
|
||||||
|
draft: Persona[]
|
||||||
|
favorite: Map<string, Persona>
|
||||||
|
all: Map<string, Persona>
|
||||||
|
loading: boolean
|
||||||
|
}>
|
||||||
|
|
||||||
|
function createPersonaStore(): PersonaStore {
|
||||||
|
const store = writable({ all: new Map(), draft: [], favorite: new Map(), loading: true })
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const chitChat = {
|
||||||
|
identity: undefined,
|
||||||
|
name: 'Chit Chat',
|
||||||
|
pitch: 'We pretty much just say gm all the time.',
|
||||||
|
description: 'We pretty much just say gm all the time.',
|
||||||
|
postsCount: 125,
|
||||||
|
picture:
|
||||||
|
'https://upload.wikimedia.org/wikipedia/commons/4/42/Chit_chat_%28256889331%29.jpg?20191121211426',
|
||||||
|
}
|
||||||
|
const expats = {
|
||||||
|
identity: undefined,
|
||||||
|
name: 'Expats',
|
||||||
|
pitch: 'Different countries, same work...',
|
||||||
|
description: 'Different countries, same work...',
|
||||||
|
postsCount: 4,
|
||||||
|
picture: 'https://upload.wikimedia.org/wikipedia/commons/8/88/British_expats_countrymap.svg',
|
||||||
|
}
|
||||||
|
const cats = {
|
||||||
|
identity: undefined,
|
||||||
|
name: 'Cats',
|
||||||
|
pitch: "Yeah it's the internet, what did you expect?",
|
||||||
|
description: "Yeah it's the internet, what did you expect?",
|
||||||
|
postsCount: 5128,
|
||||||
|
picture:
|
||||||
|
'https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/Adorable-animal-cat-20787.jpg/1599px-Adorable-animal-cat-20787.jpg?20180518085718',
|
||||||
|
}
|
||||||
|
const geoPolitics = {
|
||||||
|
identity: undefined,
|
||||||
|
name: 'Geo Politics',
|
||||||
|
pitch: `Group full of "seen it all's"`,
|
||||||
|
description: `Group full of "seen it all's"`,
|
||||||
|
postsCount: 53,
|
||||||
|
picture:
|
||||||
|
'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/World_geopolitical_chess.png/1600px-World_geopolitical_chess.png?20200226194321',
|
||||||
|
}
|
||||||
|
const controversy = {
|
||||||
|
identity: undefined,
|
||||||
|
name: 'Controversy',
|
||||||
|
pitch: '...',
|
||||||
|
description: '...',
|
||||||
|
postsCount: 9999,
|
||||||
|
picture:
|
||||||
|
'https://upload.wikimedia.org/wikipedia/en/e/ea/Controversy_legend.gif?20060220215816',
|
||||||
|
}
|
||||||
|
|
||||||
|
const all = new Map<string, Persona>()
|
||||||
|
const favorite = new Map<string, Persona>()
|
||||||
|
all.set('1', chitChat)
|
||||||
|
all.set('2', expats)
|
||||||
|
all.set('3', cats)
|
||||||
|
all.set('4', geoPolitics)
|
||||||
|
all.set('5', controversy)
|
||||||
|
|
||||||
|
favorite.set('3', cats)
|
||||||
|
favorite.set('4', controversy)
|
||||||
|
|
||||||
|
store.set({ draft: [], all, favorite, loading: false })
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
export const personas = createPersonaStore()
|
@ -5,7 +5,6 @@ import { subscribeToPosts } from '$lib/services/posts'
|
|||||||
export interface Post {
|
export interface Post {
|
||||||
timestamp: number
|
timestamp: number
|
||||||
text: string
|
text: string
|
||||||
tx: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PostData {
|
interface PostData {
|
||||||
@ -29,7 +28,6 @@ async function fetchPosts() {
|
|||||||
posts.add({
|
posts.add({
|
||||||
text: post.text,
|
text: post.text,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
tx: '',
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import { writable, type Writable } from 'svelte/store'
|
import { writable, type Writable } from 'svelte/store'
|
||||||
import type { Signer } from 'ethers'
|
import type { Signer } from 'ethers'
|
||||||
import type { Identity } from '@semaphore-protocol/identity'
|
|
||||||
|
|
||||||
export interface Profile {
|
export interface Profile {
|
||||||
signer?: Signer
|
signer?: Signer
|
||||||
identities: Record<string, Identity>
|
address?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileStore = Writable<Profile>
|
export type ProfileStore = Writable<Profile>
|
||||||
|
|
||||||
function createProfileStore(): ProfileStore {
|
function createProfileStore(): ProfileStore {
|
||||||
return writable<Profile>({ identities: {} })
|
return writable<Profile>({})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const profile = createProfileStore()
|
export const profile = createProfileStore()
|
||||||
|
@ -1,115 +1,104 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// import Header from '$lib/components/header.svelte'
|
|
||||||
import HeaderTop from '$lib/components/header-top.svelte'
|
import HeaderTop from '$lib/components/header-top.svelte'
|
||||||
import HeaderDescription from '$lib/components/header-description.svelte'
|
import Persona from '$lib/components/persona.svelte'
|
||||||
import Post from '$lib/components/post.svelte'
|
|
||||||
import Button from '$lib/components/button.svelte'
|
|
||||||
import WalletConnect from '$lib/components/wallet-connect.svelte'
|
|
||||||
import Edit from '$lib/components/icons/edit.svelte'
|
|
||||||
|
|
||||||
import { posts } from '$lib/stores/post'
|
|
||||||
import { profile } from '$lib/stores/profile'
|
import { profile } from '$lib/stores/profile'
|
||||||
|
import { personas } from '$lib/stores/persona'
|
||||||
|
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import { browser } from '$app/environment'
|
import { ROUTES } from '$lib/routes'
|
||||||
import Masonry from '$lib/masonry.svelte'
|
|
||||||
|
|
||||||
let windowWidth: number = browser ? window.innerWidth : 0
|
import Button from '$lib/components/button.svelte'
|
||||||
|
import Search from '$lib/components/icons/search.svelte'
|
||||||
|
import SettingsView from '$lib/components/icons/settings-view.svelte'
|
||||||
|
import { chats } from '$lib/stores/chat'
|
||||||
|
import Add from '$lib/components/icons/add.svelte'
|
||||||
|
|
||||||
function getMasonryColumnWidth(windowInnerWidth: number) {
|
let filterText = ''
|
||||||
if (windowInnerWidth < 739) {
|
let showChat = false
|
||||||
return '100%'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowInnerWidth < 1060) {
|
|
||||||
return 'minmax(min(100%/2, max(320px, 100%/2)), 1fr)'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowInnerWidth < 1381) {
|
|
||||||
return 'minmax(min(100%/3, max(320px, 100%/3)), 1fr)'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowInnerWidth < 1702) {
|
|
||||||
return 'minmax(min(100%/4, max(320px, 100%/4)), 1fr)'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowInnerWidth < 2023) {
|
|
||||||
return 'minmax(min(100%/5, max(320px, 100%/5)), 1fr)'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowInnerWidth < 2560) {
|
|
||||||
return 'minmax(min(100%/6, max(320px, 100%/6)), 1fr)'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowInnerWidth < 3009) {
|
|
||||||
return 'minmax(min(100%/7, max(320px, 100%/7)), 1fr)'
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'minmax(323px, 1fr)'
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window bind:innerWidth={windowWidth} />
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<!-- <Header loggedin={$profile.signer !== undefined} /> -->
|
<HeaderTop address={$profile.address} />
|
||||||
|
|
||||||
<HeaderTop loggedin={$profile.signer !== undefined} />
|
|
||||||
<HeaderDescription />
|
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{#if $profile.signer !== undefined}
|
{#if $profile.signer !== undefined}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="new-post-button" on:click={() => goto('/post/new')}>
|
<div class="nav">
|
||||||
Share freely...
|
<div class={showChat ? '' : 'active'} on:click={() => (showChat = false)}>Personas</div>
|
||||||
<Button variant="primary" label="Create post" icon={Edit} />
|
<div class={showChat ? 'active' : ''} on:click={() => (showChat = true)}>
|
||||||
|
Chats
|
||||||
|
{#if $chats.unread > 0}
|
||||||
|
<div class="unread">{$chats.unread}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
|
||||||
<WalletConnect />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $posts.loading}
|
{#if showChat}
|
||||||
<p>Loading posts...</p>
|
<div>Chat not implemented yet</div>
|
||||||
{:else if $posts.posts.length == 0}
|
{:else if $personas.loading}
|
||||||
<p>There are no posts yet</p>
|
<p>Loading personas...</p>
|
||||||
{:else}
|
{:else}
|
||||||
<Masonry gridGap="0" colWidth={getMasonryColumnWidth(windowWidth)} items={$posts.posts}>
|
{#if $personas.draft.length !== 0 && $profile.signer !== undefined}
|
||||||
{#each $posts.posts as post}
|
<div class="subtitle">Draft personas</div>
|
||||||
<Post {post} />
|
<Button icon={Add} label="Create persona" on:click={() => goto(ROUTES.PERSONA_NEW)} />
|
||||||
|
<div class="grid">
|
||||||
|
{#each $personas.draft as draftPersona, index}
|
||||||
|
<Persona
|
||||||
|
name={draftPersona.name}
|
||||||
|
description={draftPersona.description}
|
||||||
|
postsCount={draftPersona.postsCount}
|
||||||
|
on:click={() => goto(ROUTES.PERSONA(index.toFixed()))}
|
||||||
|
picture={draftPersona.picture}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $personas.favorite.size !== 0 && $profile.signer !== undefined}
|
||||||
|
<div class="subtitle">Favorites</div>
|
||||||
|
<div class="grid">
|
||||||
|
{#each [...$personas.favorite] as [groupId, data]}
|
||||||
|
<Persona
|
||||||
|
name={data.name}
|
||||||
|
description={data.description}
|
||||||
|
postsCount={data.postsCount}
|
||||||
|
on:click={() => goto(ROUTES.PERSONA(groupId))}
|
||||||
|
picture={data.picture}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="subtitle">All personas</div>
|
||||||
|
<Search />
|
||||||
|
<input bind:value={filterText} placeholder="Search..." />
|
||||||
|
{#if $profile.signer !== undefined}
|
||||||
|
<Button icon={Add} label="Create persona" on:click={() => goto(ROUTES.PERSONA_NEW)} />
|
||||||
|
{/if}
|
||||||
|
<Button icon={SettingsView} />
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
{#each [...$personas.all].filter(([, data]) => data.name
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(filterText.toLowerCase())) as [groupId, data]}
|
||||||
|
<Persona
|
||||||
|
name={data.name}
|
||||||
|
description={data.pitch}
|
||||||
|
postsCount={data.postsCount}
|
||||||
|
on:click={() => goto(ROUTES.PERSONA(groupId))}
|
||||||
|
picture={data.picture}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<p>There are no personas yet</p>
|
||||||
{/each}
|
{/each}
|
||||||
</Masonry>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.new-post-button {
|
|
||||||
font-family: var(--font-serif);
|
|
||||||
padding: var(--spacing-24) var(--spacing-12);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: var(--spacing-12);
|
|
||||||
align-items: center;
|
|
||||||
border-top: 1px solid var(--grey-200);
|
|
||||||
border-bottom: 1px solid var(--grey-200);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
border: none;
|
|
||||||
outline: 1px solid var(--grey-200);
|
|
||||||
outline-offset: -0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
border-top-color: var(--grey-500);
|
|
||||||
border-left-color: var(--grey-500);
|
|
||||||
border-bottom-color: var(--grey-500);
|
|
||||||
outline-color: var(--grey-500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
|
|
||||||
@ -118,4 +107,49 @@
|
|||||||
margin: 0 auto 0;
|
margin: 0 auto 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
width: 450px;
|
||||||
|
height: 50px;
|
||||||
|
margin: auto;
|
||||||
|
border-radius: 25px;
|
||||||
|
background-color: #ececec;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: solid 3px #ececec;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
div {
|
||||||
|
padding: 10px;
|
||||||
|
width: 50%;
|
||||||
|
border-radius: 25px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.active {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unread {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
width: min-content;
|
||||||
|
margin-left: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-columns: auto;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
grid-auto-rows: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
98
packages/ui/src/routes/persona/[id]/+page.svelte
Normal file
98
packages/ui/src/routes/persona/[id]/+page.svelte
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import HeaderTop from '$lib/components/header-top.svelte'
|
||||||
|
import Post from '$lib/components/post.svelte'
|
||||||
|
import Button from '$lib/components/button.svelte'
|
||||||
|
import Edit from '$lib/components/icons/edit.svelte'
|
||||||
|
|
||||||
|
import { posts } from '$lib/stores/post'
|
||||||
|
import { profile } from '$lib/stores/profile'
|
||||||
|
import { goto } from '$app/navigation'
|
||||||
|
import { browser } from '$app/environment'
|
||||||
|
import { page } from '$app/stores'
|
||||||
|
import Masonry from '$lib/masonry.svelte'
|
||||||
|
import { ROUTES } from '$lib/routes'
|
||||||
|
|
||||||
|
let windowWidth: number = browser ? window.innerWidth : 0
|
||||||
|
|
||||||
|
function getMasonryColumnWidth(windowInnerWidth: number) {
|
||||||
|
if (windowInnerWidth < 739) return '100%'
|
||||||
|
if (windowInnerWidth < 1060) return 'minmax(min(100%/2, max(320px, 100%/2)), 1fr)'
|
||||||
|
if (windowInnerWidth < 1381) return 'minmax(min(100%/3, max(320px, 100%/3)), 1fr)'
|
||||||
|
if (windowInnerWidth < 1702) return 'minmax(min(100%/4, max(320px, 100%/4)), 1fr)'
|
||||||
|
if (windowInnerWidth < 2023) return 'minmax(min(100%/5, max(320px, 100%/5)), 1fr)'
|
||||||
|
if (windowInnerWidth < 2560) return 'minmax(min(100%/6, max(320px, 100%/6)), 1fr)'
|
||||||
|
if (windowInnerWidth < 3009) return 'minmax(min(100%/7, max(320px, 100%/7)), 1fr)'
|
||||||
|
return 'minmax(323px, 1fr)'
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDraft = $page.url.searchParams.has('draft')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window bind:innerWidth={windowWidth} />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<HeaderTop address={$profile.address} />
|
||||||
|
|
||||||
|
<Button label="GO BACK" on:click={() => goto(ROUTES.HOME)} />
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
{#if $profile.signer !== undefined}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div class="new-post-button" on:click={() => goto(ROUTES.POST_NEW($page.params.id))}>
|
||||||
|
Share freely...
|
||||||
|
<Button variant="primary" label="Create post" icon={Edit} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $posts.loading}
|
||||||
|
<p>Loading posts...</p>
|
||||||
|
{:else if $posts.posts.length == 0}
|
||||||
|
<p>There are no posts yet</p>
|
||||||
|
{:else}
|
||||||
|
<Masonry gridGap="0" colWidth={getMasonryColumnWidth(windowWidth)} items={$posts.posts}>
|
||||||
|
{#each $posts.posts as post}
|
||||||
|
<Post {post} />
|
||||||
|
{/each}
|
||||||
|
</Masonry>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.new-post-button {
|
||||||
|
font-family: var(--font-serif);
|
||||||
|
padding: var(--spacing-24) var(--spacing-12);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--spacing-12);
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1px solid var(--grey-200);
|
||||||
|
border-bottom: 1px solid var(--grey-200);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
border: none;
|
||||||
|
outline: 1px solid var(--grey-200);
|
||||||
|
outline-offset: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
border-top-color: var(--grey-500);
|
||||||
|
border-left-color: var(--grey-500);
|
||||||
|
border-bottom-color: var(--grey-500);
|
||||||
|
outline-color: var(--grey-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.wrapper {
|
||||||
|
margin-left: -1px;
|
||||||
|
|
||||||
|
@media (min-width: 739px) {
|
||||||
|
padding: 0 var(--spacing-48);
|
||||||
|
margin: 0 auto 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -7,10 +7,13 @@
|
|||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import { ROUTES } from '$lib/routes'
|
import { ROUTES } from '$lib/routes'
|
||||||
import {
|
import {
|
||||||
|
createIdentity,
|
||||||
generateGroupProof,
|
generateGroupProof,
|
||||||
getContractGroup,
|
getContractGroup,
|
||||||
getGlobalAnonymousFeed,
|
getGlobalAnonymousFeed,
|
||||||
getRandomExternalNullifier,
|
getRandomExternalNullifier,
|
||||||
|
joinGroupOffChain,
|
||||||
|
joinGroupOnChain,
|
||||||
} from '$lib/services/index'
|
} from '$lib/services/index'
|
||||||
import { posts } from '$lib/stores/post'
|
import { posts } from '$lib/stores/post'
|
||||||
import { hashPost, createPost } from '$lib/services/posts'
|
import { hashPost, createPost } from '$lib/services/posts'
|
||||||
@ -27,12 +30,21 @@
|
|||||||
const signer = $profile.signer
|
const signer = $profile.signer
|
||||||
if (!signer) throw new Error('no signer')
|
if (!signer) throw new Error('no signer')
|
||||||
|
|
||||||
const identity = $profile.identities.anonymous
|
const defaultIdentity = 'anonymous'
|
||||||
if (!identity) throw new Error('no identity')
|
|
||||||
|
const identity = await createIdentity(signer, defaultIdentity)
|
||||||
|
|
||||||
const globalAnonymousFeed = getGlobalAnonymousFeed(signer)
|
const globalAnonymousFeed = getGlobalAnonymousFeed(signer)
|
||||||
const group = await getContractGroup(globalAnonymousFeed)
|
const group = await getContractGroup(globalAnonymousFeed)
|
||||||
|
|
||||||
|
const commitment = identity.commitment
|
||||||
|
|
||||||
|
if (!group.members.includes(commitment)) {
|
||||||
|
joinGroupOffChain(group, commitment)
|
||||||
|
const txres = await joinGroupOnChain(globalAnonymousFeed, commitment)
|
||||||
|
console.log(txres)
|
||||||
|
}
|
||||||
|
|
||||||
const post = { text: postText }
|
const post = { text: postText }
|
||||||
const signal = hashPost(post)
|
const signal = hashPost(post)
|
||||||
|
|
||||||
@ -45,7 +57,6 @@
|
|||||||
posts.add({
|
posts.add({
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
text: postText,
|
text: postText,
|
||||||
tx: '',
|
|
||||||
})
|
})
|
||||||
goto(ROUTES.HOME)
|
goto(ROUTES.HOME)
|
||||||
} catch (error) {
|
} catch (error) {
|
35
packages/ui/src/routes/persona/new/+page.svelte
Normal file
35
packages/ui/src/routes/persona/new/+page.svelte
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Undo from '$lib/components/icons/undo.svelte'
|
||||||
|
import Button from '$lib/components/button.svelte'
|
||||||
|
import { personas } from '$lib/stores/persona'
|
||||||
|
|
||||||
|
let name = ''
|
||||||
|
let pitch = ''
|
||||||
|
let description = ''
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button icon={Undo} on:click={() => history.back()} />
|
||||||
|
<span>Create persona</span>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Persona name
|
||||||
|
<input type="text" bind:value={name} placeholder="Enter a short memorable name…" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Persona pitch
|
||||||
|
<textarea bind:value={pitch} />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Persona description
|
||||||
|
<textarea bind:value={description} />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<Button label="Cancel" on:click={() => history.back()} />
|
||||||
|
<Button
|
||||||
|
label="Proceed"
|
||||||
|
on:click={() => {
|
||||||
|
$personas.draft
|
||||||
|
}}
|
||||||
|
/>
|
@ -6,15 +6,7 @@
|
|||||||
import Wallet from '$lib/components/icons/wallet.svelte'
|
import Wallet from '$lib/components/icons/wallet.svelte'
|
||||||
import WalletInfo from '$lib/components/wallet-info.svelte'
|
import WalletInfo from '$lib/components/wallet-info.svelte'
|
||||||
import { formatAddress } from '$lib/utils'
|
import { formatAddress } from '$lib/utils'
|
||||||
import {
|
import { connectWallet, canConnectWallet } from '$lib/services'
|
||||||
connectWallet,
|
|
||||||
canConnectWallet,
|
|
||||||
createIdentity,
|
|
||||||
getGlobalAnonymousFeed,
|
|
||||||
getContractGroup,
|
|
||||||
joinGroupOffChain,
|
|
||||||
joinGroupOnChain,
|
|
||||||
} from '$lib/services'
|
|
||||||
import { profile } from '$lib/stores/profile'
|
import { profile } from '$lib/stores/profile'
|
||||||
|
|
||||||
let y: number
|
let y: number
|
||||||
@ -25,24 +17,9 @@
|
|||||||
const handleConnect = async () => {
|
const handleConnect = async () => {
|
||||||
try {
|
try {
|
||||||
const signer = await connectWallet()
|
const signer = await connectWallet()
|
||||||
$profile.signer = signer
|
const address = await signer.getAddress()
|
||||||
|
|
||||||
const defaultIdentity = 'anonymous'
|
$profile = { signer, address }
|
||||||
|
|
||||||
const identity = await createIdentity(signer, defaultIdentity)
|
|
||||||
|
|
||||||
$profile.identities = { ...$profile.identities, [defaultIdentity]: identity }
|
|
||||||
|
|
||||||
const globalAnonymousFeed = getGlobalAnonymousFeed(signer)
|
|
||||||
const group = await getContractGroup(globalAnonymousFeed)
|
|
||||||
|
|
||||||
const commitment = identity.commitment
|
|
||||||
|
|
||||||
if (!group.members.includes(commitment)) {
|
|
||||||
joinGroupOffChain(group, commitment)
|
|
||||||
const txres = await joinGroupOnChain(globalAnonymousFeed, commitment)
|
|
||||||
console.log(txres)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err as Error
|
error = err as Error
|
||||||
}
|
}
|
||||||
@ -98,20 +75,11 @@
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
icon={Logout}
|
icon={Logout}
|
||||||
label="Logout"
|
label="Logout"
|
||||||
on:click={() => ($profile.signer = undefined)}
|
on:click={() => ($profile = {})}
|
||||||
disabled={!$profile.signer}
|
disabled={!$profile.signer}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
{#each Object.entries($profile.identities) as [name, identity]}
|
|
||||||
<div>{name}</div>
|
|
||||||
<div>commitment: {identity.getCommitment().toString(16)}</div>
|
|
||||||
<div>nullifier: {identity.getNullifier().toString(16)}</div>
|
|
||||||
<div>trapdoor: {identity.getTrapdoor().toString(16)}</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -192,13 +160,4 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.info {
|
|
||||||
padding: var(--spacing-12);
|
|
||||||
max-width: 100%;
|
|
||||||
word-wrap: break-word;
|
|
||||||
|
|
||||||
> div:not(:first-child) {
|
|
||||||
margin-top: var(--spacing-12);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -3,5 +3,5 @@ import { expect, test } from '@playwright/test'
|
|||||||
|
|
||||||
test('index page has expected header', async ({ page }) => {
|
test('index page has expected header', async ({ page }) => {
|
||||||
await page.goto(ROUTES.HOME)
|
await page.goto(ROUTES.HOME)
|
||||||
expect(await page.textContent('span')).toBe('The Outlet')
|
expect(await page.textContent('span')).toBe('Kurate')
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user