Add Follows step to create Account

This commit is contained in:
Bojan Mojsilovic 2023-10-13 11:52:53 +02:00
parent 13de3e001a
commit dbfbebcb7e
9 changed files with 244 additions and 7 deletions

View File

@ -13,6 +13,7 @@ import styles from './Buttons.module.scss';
const ButtonFollow: Component<{ const ButtonFollow: Component<{
person: PrimalUser | undefined, person: PrimalUser | undefined,
id?: string, id?: string,
onFollow?: (person: PrimalUser | undefined) => void,
then?: (remove: boolean, pubkey: string) => void, then?: (remove: boolean, pubkey: string) => void,
}> = (props) => { }> = (props) => {
@ -28,6 +29,12 @@ const ButtonFollow: Component<{
const onFollow = (e: MouseEvent) => { const onFollow = (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
if (props.onFollow) {
props.onFollow(props.person);
return
}
if (!account || !account.hasPublicKey() || !props.person) { if (!account || !account.hasPublicKey() || !props.person) {
toast?.sendWarning(intl.formatMessage(t.needToLogin)) toast?.sendWarning(intl.formatMessage(t.needToLogin))
return; return;

View File

@ -0,0 +1,26 @@
import { Button } from '@kobalte/core';
import { Component, JSXElement } from 'solid-js';
import { hookForDev } from '../../lib/devTools';
import styles from './Buttons.module.scss';
const ButtonTertiary: Component<{
id?: string,
onClick?: (e: MouseEvent) => void,
children?: JSXElement,
disabled?: boolean,
}> = (props) => {
return (
<Button.Root
id={props.id}
class={styles.tertiary}
onClick={props.onClick}
disabled={props.disabled}
>
{props.children}
</Button.Root>
)
}
export default hookForDev(ButtonTertiary);

View File

@ -33,6 +33,8 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding-inline: 10px;
padding-block: 6px;
} }
} }
@ -187,3 +189,20 @@
box-shadow: none; box-shadow: none;
} }
} }
.tertiary {
border-radius: 6px;
padding-inline: 10px;
padding-block: 6px;
font-size: 12px;
line-height: 16px;
font-weight: 700;
margin: 0px;
color: var(--text-primary);
background: var(--background-card);
border: 1px solid var(--subtile-devider);
&:hover {
border: 1px solid var(--text-tertiary);
}
}

View File

@ -26,9 +26,21 @@
} }
.leftContent { .leftContent {
position: relative;
display: grid; display: grid;
height: 100%; height: 100%;
justify-content: right; justify-content: right;
.overlay {
position: absolute;
top: 0;
left: 0;
width: calc(100% + 18px);
height: 100%;
background-color: var(--background-site);
opacity: 0.8;
z-index: var(--z-index-overlay);
}
} }
.leftFooter { .leftFooter {

View File

@ -1,4 +1,4 @@
import { Component, createEffect, createSignal, onCleanup, onMount } from 'solid-js'; import { Component, createEffect, createSignal, onCleanup, onMount, Show } from 'solid-js';
import styles from './Layout.module.scss'; import styles from './Layout.module.scss';
@ -47,6 +47,7 @@ const Layout: Component = () => {
onMount(() => { onMount(() => {
window.addEventListener('resize', onResize); window.addEventListener('resize', onResize);
console.log('OVERLAY: ', location)
}); });
onCleanup(() => { onCleanup(() => {
@ -100,6 +101,9 @@ const Layout: Component = () => {
<div class={styles.leftContent}> <div class={styles.leftContent}>
<NavMenu /> <NavMenu />
<Show when={location.pathname === '/new'}>
<div class={styles.overlay}></div>
</Show>
</div> </div>
<div class={styles.leftFooter}> <div class={styles.leftFooter}>

View File

@ -128,6 +128,11 @@ export function AccountProvider(props: { children: JSXElement }) {
}; };
const setSec = (sec: string | undefined) => { const setSec = (sec: string | undefined) => {
if (!sec) {
logout();
return;
}
const decoded = nip19.decode(sec); const decoded = nip19.decode(sec);
if (decoded.type === 'nsec' && decoded.data) { if (decoded.type === 'nsec' && decoded.data) {

View File

@ -290,6 +290,7 @@
--z-index-lifted: 10; --z-index-lifted: 10;
--z-index-header: 20; --z-index-header: 20;
--z-index-floater: 30; --z-index-floater: 30;
--z-index-overlay: 40;
--sidebar-section-icon-gradient: linear-gradient(175.11deg, #FA9A43 6.94%, #FA4343 29.79%, #5B12A4 97.76%), linear-gradient(170.29deg, #CCCCCC 12.73%, #808080 94.98%), #D9D9D9; --sidebar-section-icon-gradient: linear-gradient(175.11deg, #FA9A43 6.94%, #FA4343 29.79%, #5B12A4 97.76%), linear-gradient(170.29deg, #CCCCCC 12.73%, #808080 94.98%), #D9D9D9;

View File

@ -445,3 +445,65 @@ form {
} }
} }
.recomendedFollowsCaption {
display: flex;
justify-content: space-between;
padding: 10px 5px 10px 5px;
background-color: var(--background-card);
border-radius: 8px;
height: 54px;
.caption {
color: var(--text-secondary);
font-size: 14px;
font-weight: 500;
line-height: 16px;
text-transform: uppercase;
}
button {
margin-block: 0;
}
}
.userToFollow {
display: flex;
justify-content: space-between;
align-items: center;
padding-block: 10px;
padding-inline: 8px;
border-bottom: 1px solid var(--background-input);
.info {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
.nameAndNip05 {
display: flex;
flex-direction: column;
.name {
color: var(--text-primary);
font-size: 14px;
font-weight: 700;
line-height: 12px;
}
.nip05 {
color: var(--text-tertiary-2);
font-size: 12px;
font-weight: 400;
line-height: 16px;
}
}
}
.action {
display: flex;
align-items: center;
button {
width: fit-content;
margin: 0;
}
}
}

View File

@ -1,6 +1,6 @@
import { useIntl } from '@cookbook/solid-intl'; import { useIntl } from '@cookbook/solid-intl';
import { useNavigate } from '@solidjs/router'; import { useNavigate } from '@solidjs/router';
import { Component, createEffect, createSignal, onMount, Show } from 'solid-js'; import { Component, createEffect, createSignal, For, onMount, Show } from 'solid-js';
import { APP_ID } from '../App'; import { APP_ID } from '../App';
import Avatar from '../components/Avatar/Avatar'; import Avatar from '../components/Avatar/Avatar';
import Loader from '../components/Loader/Loader'; import Loader from '../components/Loader/Loader';
@ -12,7 +12,7 @@ import { useAccountContext } from '../contexts/AccountContext';
import { useMediaContext } from '../contexts/MediaContext'; import { useMediaContext } from '../contexts/MediaContext';
import { useProfileContext } from '../contexts/ProfileContext'; import { useProfileContext } from '../contexts/ProfileContext';
import { uploadMedia } from '../lib/media'; import { uploadMedia } from '../lib/media';
import { getSuggestions, sendProfile } from '../lib/profile'; import { getProfileContactList, getSuggestions, getUserProfiles, sendProfile } from '../lib/profile';
import { subscribeTo as uploadSub } from "../uploadSocket"; import { subscribeTo as uploadSub } from "../uploadSocket";
import { import {
actions as tActions, actions as tActions,
@ -29,6 +29,12 @@ import { hexToNpub, hexToNsec } from '../lib/keys';
import { storeSec } from '../lib/localStore'; import { storeSec } from '../lib/localStore';
import { getPreConfiguredRelays } from '../lib/relays'; import { getPreConfiguredRelays } from '../lib/relays';
import CreatePinModal from '../components/CreatePinModal/CreatePinModal'; import CreatePinModal from '../components/CreatePinModal/CreatePinModal';
import { useSearchContext } from '../contexts/SearchContext';
import ButtonFollow from '../components/Buttons/ButtonFollow';
import ButtonTertiary from '../components/Buttons/ButtonTertiary';
import { sendContacts } from '../lib/notes';
import ButtonSecondary from '../components/Buttons/ButtonSecondary';
import { nip05Verification } from '../stores/profile';
type AutoSizedTextArea = HTMLTextAreaElement & { _baseScrollHeight: number }; type AutoSizedTextArea = HTMLTextAreaElement & { _baseScrollHeight: number };
@ -36,6 +42,7 @@ const CreateAccount: Component = () => { const intl = useIntl();
const profile = useProfileContext(); const profile = useProfileContext();
const media = useMediaContext(); const media = useMediaContext();
const account = useAccountContext(); const account = useAccountContext();
const search = useSearchContext();
const toast = useToastContext(); const toast = useToastContext();
const navigate = useNavigate(); const navigate = useNavigate();
@ -203,6 +210,8 @@ const CreateAccount: Component = () => { const intl = useIntl();
return false; return false;
} }
const pubkey = account.publicKey;
const data = new FormData(e.target as HTMLFormElement); const data = new FormData(e.target as HTMLFormElement);
const name = data.get('name')?.toString() || ''; const name = data.get('name')?.toString() || '';
@ -234,11 +243,21 @@ const CreateAccount: Component = () => { const intl = useIntl();
} }
}); });
const { success } = await sendProfile({ ...metadata }, account.relays, relaySettings); const { success } = await sendProfile({ ...metadata }, account.relays, relaySettings);
if (success) { if (success) {
toast?.sendSuccess(intl.formatMessage(tToast.updateProfileSuccess)); toast?.sendSuccess(intl.formatMessage(tToast.updateProfileSuccess));
pubkey && getUserProfiles([pubkey], `user_profile_${APP_ID}`);
const tags = followed.map(pk => ['p', pk]);
const relayInfo = JSON.stringify(relaySettings);
const date = Math.floor((new Date()).getTime() / 1000);
const { success: succ } = await sendContacts(tags, date, relayInfo, account.relays, relaySettings);
if (succ) {
getProfileContactList(account?.publicKey, `user_contacts_${APP_ID}`);
}
setShowCreatePin(true); setShowCreatePin(true);
@ -289,7 +308,8 @@ const CreateAccount: Component = () => { const intl = useIntl();
setTempNsec(nsec); setTempNsec(nsec);
setCreatedAccount(() => ({ sec: nsec, pubkey })); setCreatedAccount(() => ({ sec: nsec, pubkey }));
getSuggestions() getSuggestions();
search?.actions.getRecomendedUsers();
}); });
@ -305,6 +325,37 @@ const CreateAccount: Component = () => { const intl = useIntl();
setShowCreatePin(false); setShowCreatePin(false);
} }
const clearNewAccount = () => {
account?.actions.setSec(undefined);
setTempNsec(undefined);
setCreatedAccount(reconcile({}));
navigate('/');
}
const [followed, setFollowed] = createStore<string[]>([])
const isFollowingAllProminent = () => {
return !search?.users.some((u) => !followed.includes(u.pubkey));
};
const onFollowProminent = () => {
const pubkeys = search?.users.map(u => u.pubkey) || [];
setFollowed(() => [ ...pubkeys ]);
};
const onUnfollowProminent = () => {
setFollowed(() => []);
};
const onFollow = (pubkey: string) => {
setFollowed(followed.length, () => pubkey);
}
const onUnfollow = (pubkey: string) => {
const follows = followed.filter(f => f !== pubkey)
setFollowed(() => [...follows]);
}
return ( return (
<div class={styles.container}> <div class={styles.container}>
@ -518,8 +569,58 @@ const CreateAccount: Component = () => { const intl = useIntl();
</div> </div>
<div class={currentStep() === 'follow' ? '' : 'invisible'}> <div class={currentStep() === 'follow' ? '' : 'invisible'}>
<div class={styles.recomendedFollowsCaption}>
<div class={styles.caption}>
Prominent Nostriches
</div>
<div class={styles.action}>
<Show
when={isFollowingAllProminent()}
fallback={
<ButtonSecondary onClick={onFollowProminent}>
<span>Follow All</span>
</ButtonSecondary>
}
>
<ButtonTertiary onClick={onUnfollowProminent}>
Unfollow all
</ButtonTertiary>
</Show>
</div>
</div>
<div> <div>
HERE BE FOLLOWS <For each={search?.users}>
{user => (
<div class={styles.userToFollow}>
<div class={styles.info}>
<Avatar user={user} />
<div class={styles.nameAndNip05}>
<div class={styles.name}>
{user.name}
</div>
<div class={styles.nip05}>
{nip05Verification(user)}
</div>
</div>
</div>
<div class={styles.action}>
<Show
when={followed.includes(user.pubkey)}
fallback={
<ButtonSecondary onClick={() => onFollow(user.pubkey)}>
Follow
</ButtonSecondary>
}
>
<ButtonTertiary onClick={() => onUnfollow(user.pubkey)}>
Unfollow
</ButtonTertiary>
</Show>
</div>
</div>
)}
</For>
</div> </div>
</div> </div>
@ -561,7 +662,7 @@ const CreateAccount: Component = () => { const intl = useIntl();
<button <button
type='button' type='button'
class={styles.secondaryButton} class={styles.secondaryButton}
onClick={() => navigate('/profile')} onClick={clearNewAccount}
> >
<div> <div>
<span>{intl.formatMessage(tActions.cancel)}</span> <span>{intl.formatMessage(tActions.cancel)}</span>