login and onboarding fixes

This commit is contained in:
Alejandro Gomez 2023-02-12 13:31:48 +01:00 committed by Kieran
parent 73957e6510
commit 4f222fb813
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
28 changed files with 346 additions and 144 deletions

View File

@ -0,0 +1,12 @@
import { useNavigate } from "react-router-dom";
const Logo = () => {
const navigate = useNavigate();
return (
<h1 className="logo" onClick={() => navigate("/")}>
Snort
</h1>
);
};
export default Logo;

View File

@ -40,9 +40,25 @@
.nip05 .domain[data-domain="nostriches.net"] {
color: var(--highlight);
background-color: var(--highlight);
text-overflow: ellipsis;
}
.nip05 .badge {
color: var(--highlight);
margin: 0.1em 0.2em;
margin-left: 0.1em;
}
@media (max-width: 520px) {
.nip05 .nick {
display: none;
}
.nip05 .domain {
display: none;
}
.nip05 .badge svg {
width: 13px;
height: 13px;
margin-left: 0.15em;
margin-bottom: 1px;
}
}

View File

@ -56,10 +56,10 @@ const Nip05 = ({ nip05, pubkey, verifyNip = true }: Nip05Params) => {
return (
<div className={`flex nip05${couldNotVerify ? " failed" : ""}`} onClick={ev => ev.stopPropagation()}>
{!isDefaultUser && isVerified && <div className="nick">{`${name}@`}</div>}
{!isDefaultUser && isVerified && <span className="nick">{`${name}@`}</span>}
{isVerified && (
<>
<span className="domain" data-domain={domain?.toLowerCase()}>
<span className="domain f-ellipsis" data-domain={domain?.toLowerCase()}>
{domain}
</span>
<span className="badge">

View File

@ -229,7 +229,13 @@ export default function Nip5Service(props: Nip05ServiceProps) {
{error && <b className="error">{error.error}</b>}
{!registerStatus && (
<div className="flex mb10">
<input type="text" placeholder={formatMessage(messages.Handle)} value={handle} onChange={onHandleChange} />
<input
type="text"
className="nip-handle"
placeholder={formatMessage(messages.Handle)}
value={handle}
onChange={onHandleChange}
/>
&nbsp;@&nbsp;
<select value={domain} onChange={onDomainChange}>
{serviceConfig?.domains.map(a => (

View File

@ -197,3 +197,22 @@
border-bottom-left-radius: 0;
margin-left: -1px;
}
.note .header .nip05 .badge {
margin-top: -0.2em;
width: 13px;
height: 13px;
}
.note .reactions-link {
color: var(--font-secondary-color);
font-weight: 400;
font-size: 14px;
line-height: 24px;
margin-top: 4px;
font-feature-settings: "tnum";
}
.note .reactions-link:hover {
text-decoration: underline;
}

View File

@ -8,14 +8,14 @@ import { useIntl, FormattedMessage } from "react-intl";
import useEventPublisher from "Feed/EventPublisher";
import Bookmark from "Icons/Bookmark";
import Pin from "Icons/Pin";
import { Event as NEvent, EventKind } from "@snort/nostr";
import { parseZap } from "Element/Zap";
import ProfileImage from "Element/ProfileImage";
import Text from "Element/Text";
import { eventLink, getReactions, hexToBech32 } from "Util";
import { eventLink, getReactions, dedupeByPubkey, hexToBech32, normalizeReaction, Reaction } from "Util";
import NoteFooter, { Translation } from "Element/NoteFooter";
import NoteTime from "Element/NoteTime";
import { useUserProfiles } from "Feed/ProfileFeed";
import { TaggedRawEvent, u256, HexKey } from "@snort/nostr";
import { TaggedRawEvent, u256, HexKey, Event as NEvent, EventKind } from "@snort/nostr";
import useModeration from "Hooks/useModeration";
import { setPinned, setBookmarked } from "State/Login";
import type { RootState } from "State/Store";
@ -34,6 +34,7 @@ export interface NoteProps {
showPinned?: boolean;
showBookmarked?: boolean;
showFooter?: boolean;
showReactionsLink?: boolean;
canUnpin?: boolean;
canUnbookmark?: boolean;
};
@ -62,6 +63,7 @@ export default function Note(props: NoteProps) {
const navigate = useNavigate();
const dispatch = useDispatch();
const { data, related, highlight, options: opt, ["data-ev"]: parsedEvent, ignoreModeration = false } = props;
const [showReactions, setShowReactions] = useState(false);
const ev = useMemo(() => parsedEvent ?? new NEvent(data), [data]);
const pubKeys = useMemo(() => ev.Thread?.PubKeys || [], [ev]);
const users = useUserProfiles(pubKeys);
@ -76,6 +78,35 @@ export default function Note(props: NoteProps) {
const publisher = useEventPublisher();
const [translated, setTranslated] = useState<Translation>();
const { formatMessage } = useIntl();
const reactions = useMemo(() => getReactions(related, ev.Id, EventKind.Reaction), [related, ev]);
const groupReactions = useMemo(() => {
const result = reactions?.reduce(
(acc, reaction) => {
const kind = normalizeReaction(reaction.content);
const rs = acc[kind] || [];
return { ...acc, [kind]: [...rs, reaction] };
},
{
[Reaction.Positive]: [] as TaggedRawEvent[],
[Reaction.Negative]: [] as TaggedRawEvent[],
}
);
return {
[Reaction.Positive]: dedupeByPubkey(result[Reaction.Positive]),
[Reaction.Negative]: dedupeByPubkey(result[Reaction.Negative]),
};
}, [reactions]);
const positive = groupReactions[Reaction.Positive];
const negative = groupReactions[Reaction.Negative];
const reposts = useMemo(() => dedupeByPubkey(getReactions(related, ev.Id, EventKind.Repost)), [related, ev]);
const zaps = useMemo(() => {
const sortedZaps = getReactions(related, ev.Id, EventKind.ZapReceipt)
.map(parseZap)
.filter(z => z.valid && z.zapper !== ev.PubKey);
sortedZaps.sort((a, b) => b.amount - a.amount);
return sortedZaps;
}, [related]);
const totalReactions = positive.length + negative.length + reposts.length + zaps.length;
const options = {
showHeader: true,
@ -245,13 +276,29 @@ export default function Note(props: NoteProps) {
<div className="body" onClick={e => goToEvent(e, ev.Id)}>
{transformBody()}
{translation()}
{options.showReactionsLink && (
<div className="reactions-link" onClick={() => setShowReactions(true)}>
<FormattedMessage {...messages.ReactionsLink} values={{ n: totalReactions }} />
</div>
)}
</div>
{extendable && !showMore && (
<span className="expand-note mt10 flex f-center" onClick={() => setShowMore(true)}>
<FormattedMessage {...messages.ShowMore} />
</span>
)}
{options.showFooter && <NoteFooter ev={ev} related={related} onTranslated={t => setTranslated(t)} />}
{options.showFooter && (
<NoteFooter
ev={ev}
positive={positive}
negative={negative}
reposts={reposts}
zaps={zaps}
onTranslated={t => setTranslated(t)}
showReactions={showReactions}
setShowReactions={setShowReactions}
/>
)}
</>
);
}

View File

@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useIntl, FormattedMessage } from "react-intl";
import { Menu, MenuItem } from "@szhsin/react-menu";
@ -20,13 +20,13 @@ import Zap from "Icons/Zap";
import Reply from "Icons/Reply";
import { formatShort } from "Number";
import useEventPublisher from "Feed/EventPublisher";
import { getReactions, dedupeByPubkey, hexToBech32, normalizeReaction, Reaction } from "Util";
import { hexToBech32, normalizeReaction } from "Util";
import { NoteCreator } from "Element/NoteCreator";
import Reactions from "Element/Reactions";
import SendSats from "Element/SendSats";
import { parseZap, ZapsSummary } from "Element/Zap";
import { ParsedZap, ZapsSummary } from "Element/Zap";
import { useUserProfile } from "Feed/ProfileFeed";
import { Event as NEvent, EventKind, TaggedRawEvent, HexKey } from "@snort/nostr";
import { Event as NEvent, TaggedRawEvent, HexKey } from "@snort/nostr";
import { RootState } from "State/Store";
import { UserPreferences, setPinned, setBookmarked } from "State/Login";
import useModeration from "Hooks/useModeration";
@ -41,13 +41,18 @@ export interface Translation {
}
export interface NoteFooterProps {
related: TaggedRawEvent[];
reposts: TaggedRawEvent[];
zaps: ParsedZap[];
positive: TaggedRawEvent[];
negative: TaggedRawEvent[];
showReactions: boolean;
setShowReactions(b: boolean): void;
ev: NEvent;
onTranslated?: (content: Translation) => void;
}
export default function NoteFooter(props: NoteFooterProps) {
const { related, ev } = props;
const { ev, showReactions, setShowReactions, positive, negative, reposts, zaps } = props;
const dispatch = useDispatch();
const { formatMessage } = useIntl();
const { pinned, bookmarked } = useSelector((s: RootState) => s.login);
@ -57,49 +62,17 @@ export default function NoteFooter(props: NoteFooterProps) {
const author = useUserProfile(ev.RootPubKey);
const publisher = useEventPublisher();
const [reply, setReply] = useState(false);
const [showReactions, setShowReactions] = useState(false);
const [tip, setTip] = useState(false);
const isMine = ev.RootPubKey === login;
const lang = window.navigator.language;
const langNames = new Intl.DisplayNames([...window.navigator.languages], {
type: "language",
});
const reactions = useMemo(() => getReactions(related, ev.Id, EventKind.Reaction), [related, ev]);
const reposts = useMemo(() => dedupeByPubkey(getReactions(related, ev.Id, EventKind.Repost)), [related, ev]);
const zaps = useMemo(() => {
const sortedZaps = getReactions(related, ev.Id, EventKind.ZapReceipt)
.map(parseZap)
.filter(z => z.valid && z.zapper !== ev.PubKey);
sortedZaps.sort((a, b) => b.amount - a.amount);
return sortedZaps;
}, [related]);
const zapTotal = zaps.reduce((acc, z) => acc + z.amount, 0);
const didZap = zaps.some(a => a.zapper === login);
const groupReactions = useMemo(() => {
const result = reactions?.reduce(
(acc, reaction) => {
const kind = normalizeReaction(reaction.content);
const rs = acc[kind] || [];
if (rs.map(e => e.pubkey).includes(reaction.pubkey)) {
return acc;
}
return { ...acc, [kind]: [...rs, reaction] };
},
{
[Reaction.Positive]: [] as TaggedRawEvent[],
[Reaction.Negative]: [] as TaggedRawEvent[],
}
);
return {
[Reaction.Positive]: dedupeByPubkey(result[Reaction.Positive]),
[Reaction.Negative]: dedupeByPubkey(result[Reaction.Negative]),
};
}, [reactions]);
const positive = groupReactions[Reaction.Positive];
const negative = groupReactions[Reaction.Negative];
function hasReacted(emoji: string) {
return reactions?.some(({ pubkey, content }) => normalizeReaction(content) === emoji && pubkey === login);
return positive?.some(({ pubkey, content }) => normalizeReaction(content) === emoji && pubkey === login);
}
function hasReposted() {

View File

@ -1,7 +1,6 @@
.pfp {
display: flex;
align-items: center;
overflow: hidden;
}
.pfp .avatar-wrapper {
@ -30,3 +29,16 @@
display: flex;
flex-direction: column;
}
.pfp .display-name {
display: flex;
flex-direction: row;
align-items: center;
}
@media (min-width: 520px) {
.pfp .display-name {
align-items: flex-start;
flex-direction: column;
}
}

View File

@ -8,6 +8,7 @@ import Avatar from "Element/Avatar";
import Nip05 from "Element/Nip05";
import { HexKey } from "@snort/nostr";
import { MetadataCache } from "State/Users";
import useClientWidth from "Hooks/useClientWidth";
export interface ProfileImageProps {
pubkey: HexKey;
@ -31,6 +32,7 @@ export default function ProfileImage({
const navigate = useNavigate();
const user = useUserProfile(pubkey);
const nip05 = defaultNip ? defaultNip : user?.nip05;
const { ref, width } = useClientWidth();
const name = useMemo(() => {
return getDisplayName(user, pubkey);
@ -41,19 +43,21 @@ export default function ProfileImage({
}
return (
<div className={`pfp${className ? ` ${className}` : ""}`}>
<div className={`pfp${className ? ` ${className}` : ""}`} ref={ref}>
<div className="avatar-wrapper">
<Avatar user={user} onClick={() => navigate(link ?? profileLink(pubkey))} />
</div>
{showUsername && (
<div className="profile-name f-grow">
<div className="profile-name">
<div className="username">
<Link className="display-name" key={pubkey} to={link ?? profileLink(pubkey)}>
{name}
{nip05 && <Nip05 nip05={nip05} pubkey={pubkey} verifyNip={verifyNip} />}
</Link>
</div>
<div className="subheader">{subHeader}</div>
<div className="subheader" style={{ width: width - 80 }}>
{subHeader}
</div>
</div>
)}
</div>

View File

@ -5,11 +5,17 @@
}
.profile-preview .pfp {
flex-grow: 1;
min-width: 200px;
flex: 1 1 auto;
}
.profile-preview .about {
font-size: small;
color: var(--gray-light);
color: var(--font-secondary-color);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.profile-preview button {
min-width: 98px;
}

View File

@ -25,20 +25,22 @@ export default function ProfilePreview(props: ProfilePreviewProps) {
};
return (
<div className={`profile-preview${props.className ? ` ${props.className}` : ""}`} ref={ref}>
{inView && (
<>
<ProfileImage
pubkey={pubkey}
subHeader={options.about ? <div className="f-ellipsis about">{user?.about}</div> : undefined}
/>
{props.actions ?? (
<div className="follow-button-container">
<FollowButton pubkey={pubkey} />
</div>
)}
</>
)}
</div>
<>
<div className={`profile-preview${props.className ? ` ${props.className}` : ""}`} ref={ref}>
{inView && (
<>
<ProfileImage
pubkey={pubkey}
subHeader={options.about ? <div className="about">{user?.about}</div> : undefined}
/>
{props.actions ?? (
<div className="follow-button-container">
<FollowButton pubkey={pubkey} />
</div>
)}
</>
)}
</div>
</>
);
}

View File

@ -96,27 +96,4 @@
.reactions-view .tab.disabled {
display: none;
}
.reactions-item .reaction-icon {
width: 42px;
}
.reactions-item .avatar {
width: 21px;
height: 21px;
}
.reactions-item .pfp .username {
font-size: 14px;
}
.reactions-item .pfp .nip05 {
display: none;
}
.reactions-item button {
font-size: 14px;
}
.reactions-item .zap-reaction-icon svg {
width: 12px;
height: l2px;
}
.reactions-item .zap-amount {
font-size: 12px;
}
}

View File

@ -327,7 +327,15 @@ export default function Thread(props: ThreadProps) {
function renderRoot(note: NEvent) {
const className = `thread-root ${isSingleNote ? "thread-root-single" : ""}`;
if (note) {
return <Note className={className} key={note.Id} data-ev={note} related={notes} />;
return (
<Note
className={className}
key={note.Id}
data-ev={note}
related={notes}
options={{ showReactionsLink: true }}
/>
);
} else {
return <NoteGhost className={className}>Loading thread root.. ({notes?.length} notes loaded)</NoteGhost>;
}

View File

@ -69,7 +69,7 @@
margin-right: 0.3em;
}
.top-zap .avatar {
.top-zap .summary .pfp .avatar-wrapper .avatar {
width: 18px;
height: 18px;
}
@ -85,7 +85,7 @@
}
.amount-number {
font-weight: bold;
font-weight: 500;
}
.zap.note .body {

View File

@ -102,4 +102,5 @@ export default defineMessages({
All: { defaultMessage: "All" },
ConfirmUnbookmark: { defaultMessage: "Are you sure you want to remove this note from bookmarks?" },
ConfirmUnpin: { defaultMessage: "Are you sure you want to unpin this note?" },
ReactionsLink: { defaultMessage: "{n} Reactions" },
});

View File

@ -0,0 +1,23 @@
import { useRef, useState, useEffect } from "react";
export default function useClientWidth() {
const ref = useRef<HTMLDivElement | null>(null);
const [width, setWidth] = useState(0);
useEffect(() => {
const updateSize = () => {
if (ref.current) {
setWidth(ref.current.clientWidth);
}
};
window.addEventListener("resize", updateSize);
updateSize();
return () => window.removeEventListener("resize", updateSize);
}, [ref]);
return {
ref,
width,
};
}

View File

@ -1,6 +1,8 @@
const Badge = () => {
import IconProps from "./IconProps";
const Badge = (props: IconProps) => {
return (
<svg width="16" height="15" viewBox="0 0 16 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="16" height="15" viewBox="0 0 16 15" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M6.00004 7.50065L7.33337 8.83398L10.3334 5.83398M11.9342 2.83299C12.0714 3.16501 12.3349 3.42892 12.6667 3.5667L13.8302 4.04864C14.1622 4.18617 14.426 4.44998 14.5636 4.78202C14.7011 5.11407 14.7011 5.48715 14.5636 5.81919L14.082 6.98185C13.9444 7.31404 13.9442 7.6875 14.0824 8.01953L14.5632 9.18185C14.6313 9.34631 14.6664 9.52259 14.6665 9.70062C14.6665 9.87865 14.6315 10.0549 14.5633 10.2194C14.4952 10.3839 14.3953 10.5333 14.2694 10.6592C14.1435 10.7851 13.9941 10.8849 13.8296 10.953L12.6669 11.4346C12.3349 11.5718 12.071 11.8354 11.9333 12.1672L11.4513 13.3307C11.3138 13.6627 11.05 13.9265 10.718 14.0641C10.3859 14.2016 10.0129 14.2016 9.68085 14.0641L8.51823 13.5825C8.18619 13.4453 7.81326 13.4455 7.48143 13.5832L6.31797 14.0645C5.98612 14.2017 5.61338 14.2016 5.28162 14.0642C4.94986 13.9267 4.68621 13.6632 4.54858 13.3316L4.06652 12.1677C3.92924 11.8357 3.66574 11.5718 3.33394 11.434L2.17048 10.9521C1.8386 10.8146 1.57488 10.5509 1.4373 10.2191C1.29971 9.88724 1.29953 9.51434 1.43678 9.18235L1.91835 8.01968C2.05554 7.68763 2.05526 7.31469 1.91757 6.98284L1.43669 5.81851C1.36851 5.65405 1.3334 5.47777 1.33337 5.29974C1.33335 5.12171 1.3684 4.94542 1.43652 4.78094C1.50465 4.61646 1.60452 4.46702 1.73042 4.34115C1.85632 4.21529 2.00579 4.11546 2.17028 4.04739L3.33291 3.5658C3.66462 3.42863 3.92836 3.16545 4.06624 2.83402L4.54816 1.67052C4.68569 1.33848 4.94949 1.07467 5.28152 0.937137C5.61355 0.7996 5.98662 0.7996 6.31865 0.937137L7.48127 1.41873C7.81331 1.55593 8.18624 1.55565 8.51808 1.41795L9.68202 0.937884C10.014 0.800424 10.387 0.800452 10.719 0.937962C11.0509 1.07547 11.3147 1.3392 11.4522 1.67116L11.9343 2.835L11.9342 2.83299Z"
stroke="currentColor"

View File

@ -40,12 +40,12 @@ export default function Layout() {
useLoginFeed();
const shouldHideNoteCreator = useMemo(() => {
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate"];
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/p/"];
return hideOn.some(a => location.pathname.startsWith(a));
}, [location]);
const shouldHideHeader = useMemo(() => {
const hideOn = ["/login"];
const hideOn = ["/login", "/new"];
return hideOn.some(a => location.pathname.startsWith(a));
}, [location]);

View File

@ -3,6 +3,14 @@
height: 100vh;
overflow: hidden;
display: flex;
font-weight: 400;
font-size: 14px;
line-height: 24px;
color: #999999;
}
.light .login {
color: var(--font-tertiary-color);
}
.login > div {
@ -29,22 +37,33 @@
background-image: var(--img-src);
width: 100%;
height: 100%;
border-radius: 50px;
border-top-left-radius: 50px;
border-bottom-right-radius: 50px;
background-size: cover;
background-position-x: center;
display: flex;
align-items: flex-end;
}
.login > div:nth-child(2) > div.artwork > div {
.login > div:nth-child(2) > div.artwork > .attribution {
margin-left: 25px;
margin-right: 25px;
margin-bottom: 25px;
padding: 4px 12px;
background-color: var(--modal-bg-color);
border-radius: 1em;
border-radius: 100px;
color: var(--gray-light);
font-size: 14px;
user-select: none;
}
.artwork .artist {
background: var(--snort-gradient);
font-weight: 600;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-fill-color: transparent;
}
.login > div:nth-child(2) > div.artwork .zap-button {
@ -59,26 +78,52 @@
}
}
.login input {
background-color: var(--gray-secondary);
padding: 12px 16px;
font-size: 16px;
}
.login .login-note {
color: var(--gray-light);
font-size: 14px;
}
.login .login-actions {
margin-top: 24px;
margin-top: 32px;
}
.login .login-actions > button {
margin-right: 10px;
}
@media (max-width: 520px) {
.login .login-actions > button {
margin-bottom: 10px;
}
}
.login .login-or {
font-weight: 400;
font-size: 14px;
line-height: 24px;
margin-top: 56px;
margin-bottom: 64px;
}
.light .login-or {
color: #a1a1aa;
}
.login-container input[type="text"] {
border: none;
background-color: var(--gray-secondary);
padding: 12px 16px;
font-size: 16px;
}
.login-container h1 {
color: var(--font-color);
font-style: normal;
font-weight: 700;
font-size: 32px;
line-height: 39px;
}
.login-container h2 {
color: var(--font-color);
font-weight: 600;
font-size: 16px;
line-height: 19px;
}

View File

@ -4,7 +4,7 @@ import { CSSProperties, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import * as secp from "@noble/secp256k1";
import { FormattedMessage } from "react-intl";
import { useIntl, FormattedMessage } from "react-intl";
import { RootState } from "State/Store";
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
@ -14,6 +14,8 @@ import { HexKey } from "@snort/nostr";
import ZapButton from "Element/ZapButton";
// import useImgProxy from "Feed/ImgProxy";
import messages from "./messages";
interface ArtworkEntry {
name: string;
pubkey: HexKey;
@ -51,7 +53,8 @@ export default function LoginPage() {
const [key, setKey] = useState("");
const [error, setError] = useState("");
const [art, setArt] = useState<ArtworkEntry>();
// const { proxy } = useImgProxy();
const { formatMessage } = useIntl();
//const { proxy } = useImgProxy();
useEffect(() => {
if (publicKey) {
@ -160,17 +163,17 @@ export default function LoginPage() {
<p dir="auto">
<FormattedMessage defaultMessage="Your key" description="Label for key input" />
</p>
<div dir="auto" className="flex">
<div className="flex">
<input
dir="auto"
type="text"
placeholder="nsec / npub / nip-05 / hex private key..."
placeholder={formatMessage(messages.KeyPlaceholder)}
className="f-grow"
onChange={e => setKey(e.target.value)}
/>
</div>
{error.length > 0 ? <b className="error">{error}</b> : null}
<p dir="auto" className="login-note">
<p className="login-note">
<FormattedMessage
defaultMessage="Only the secret key can be used to publish (sign events), everything else logs you in read-only mode."
description="Explanation for public key only login is read-only"
@ -188,22 +191,20 @@ export default function LoginPage() {
</button>
{altLogins()}
</div>
<div dir="auto" className="flex login-or">
<div className="login-note">
<FormattedMessage defaultMessage="OR" description="Seperator text for Login / Generate Key" />
</div>
<div className="flex login-or">
<FormattedMessage defaultMessage="OR" description="Seperator text for Login / Generate Key" />
<div className="divider w-max"></div>
</div>
<h1 dir="auto">
<FormattedMessage defaultMessage="Create an Account" description="Heading for generate key flow" />
</h1>
<p dir="auto" className="login-note">
<p>
<FormattedMessage
defaultMessage="Generate a public / private key pair. Do not share your private key with anyone, this acts as your password. Once lost, it cannot be “reset” or recovered. Keep safe!"
description="Note about key security before generating a new key"
/>
</p>
<div dir="auto" className="tabs">
<div className="login-actions">
<button type="button" onClick={() => makeRandomKey()}>
<FormattedMessage defaultMessage="Generate Key" description="Button: Generate a new key" />
</button>
@ -211,13 +212,13 @@ export default function LoginPage() {
</div>
</div>
<div>
<div dir="auto" className="artwork" style={{ ["--img-src"]: `url('${art?.link}')` } as CSSProperties}>
<div>
<div className="artwork" style={{ ["--img-src"]: `url('${art?.link}')` } as CSSProperties}>
<div className="attribution">
<FormattedMessage
defaultMessage="Art by {name}"
description="Artwork attribution label"
values={{
name: "Karnage",
name: <span className="artist">Karnage</span>,
}}
/>
<ZapButton pubkey={art?.pubkey ?? ""} />

View File

@ -46,4 +46,5 @@ export default defineMessages({
},
Bookmarks: { defaultMessage: "Bookmarks" },
BookmarksCount: { defaultMessage: "{n} Bookmarks" },
KeyPlaceholder: { defaultMessage: "nsec, npub, nip-05, hex" },
});

View File

@ -1,6 +1,7 @@
import { useIntl, FormattedMessage } from "react-intl";
import { Link } from "react-router-dom";
import { useNavigate, Link } from "react-router-dom";
import { RecommendedFollows } from "Const";
import Logo from "Element/Logo";
import FollowListBase from "Element/FollowListBase";
import { useMemo } from "react";
@ -8,12 +9,14 @@ import messages from "./messages";
export default function DiscoverFollows() {
const { formatMessage } = useIntl();
const navigate = useNavigate();
const sortedReccomends = useMemo(() => {
return RecommendedFollows.sort(() => (Math.random() >= 0.5 ? -1 : 1));
}, []);
return (
<div className="main-content new-user" dir="auto">
<Logo />
<div className="progress-bar">
<div className="progress"></div>
</div>
@ -23,6 +26,11 @@ export default function DiscoverFollows() {
<p>
<FormattedMessage {...messages.Share} values={{ link: <Link to="/">{formatMessage(messages.World)}</Link> }} />
</p>
<div className="next-actions continue-actions">
<button type="button" onClick={() => navigate("/")}>
<FormattedMessage {...messages.Done} />{" "}
</button>
</div>
<h3>
<FormattedMessage {...messages.PopularAccounts} />
</h3>

View File

@ -3,6 +3,7 @@ import { FormattedMessage } from "react-intl";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import Logo from "Element/Logo";
import { services } from "Pages/Verification";
import Nip5Service from "Element/Nip5Service";
import ProfileImage from "Element/ProfileImage";
@ -25,6 +26,7 @@ export default function GetVerified() {
return (
<div className="main-content new-user" dir="auto">
<Logo />
<div className="progress-bar">
<div className="progress progress-third"></div>
</div>

View File

@ -3,6 +3,7 @@ import { useSelector } from "react-redux";
import { useIntl, FormattedMessage } from "react-intl";
import { ApiHost } from "Const";
import Logo from "Element/Logo";
import AsyncButton from "Element/AsyncButton";
import FollowListBase from "Element/FollowListBase";
import { RootState } from "State/Store";
@ -49,7 +50,8 @@ export default function ImportFollows() {
}
return (
<div className="main-content new-user" dir="auto">
<div className="main-content new-user">
<Logo />
<div className="progress-bar">
<div className="progress progress-last"></div>
</div>
@ -68,6 +70,16 @@ export default function ImportFollows() {
}}
/>
</p>
<div className="next-actions continue-actions">
<button className="secondary" type="button" onClick={() => navigate("/new/discover")}>
<FormattedMessage {...messages.Skip} />
</button>
<button type="button" onClick={() => navigate("/new/discover")}>
<FormattedMessage {...messages.Next} />
</button>
</div>
<h2>
<FormattedMessage {...messages.TwitterUsername} />
</h2>
@ -96,15 +108,6 @@ export default function ImportFollows() {
/>
)}
</div>
<div className="next-actions">
<button className="secondary" type="button" onClick={() => navigate("/new/discover")}>
<FormattedMessage {...messages.Skip} />
</button>
<button type="button" onClick={() => navigate("/new/discover")}>
<FormattedMessage {...messages.Next} />
</button>
</div>
</div>
);
}

View File

@ -2,6 +2,7 @@ import { useSelector } from "react-redux";
import { FormattedMessage } from "react-intl";
import { useNavigate } from "react-router-dom";
import Logo from "Element/Logo";
import { CollapsedSection } from "Element/Collapsed";
import Copy from "Element/Copy";
import { RootState } from "State/Store";
@ -72,6 +73,7 @@ export default function NewUserFlow() {
return (
<div className="main-content new-user" dir="auto">
<Logo />
<div className="progress-bar">
<div className="progress progress-first"></div>
</div>

View File

@ -2,6 +2,7 @@ import { useState } from "react";
import { useIntl, FormattedMessage } from "react-intl";
import { useNavigate } from "react-router-dom";
import Logo from "Element/Logo";
import useEventPublisher from "Feed/EventPublisher";
import messages from "./messages";
@ -23,6 +24,7 @@ export default function NewUserName() {
return (
<div className="main-content new-user" dir="auto">
<Logo />
<div className="progress-bar">
<div className="progress progress-second"></div>
</div>
@ -42,6 +44,9 @@ export default function NewUserName() {
value={username}
onChange={ev => setUsername(ev.target.value)}
/>
<div className="help-text">
<FormattedMessage defaultMessage="You can change your username at any point." />
</div>
<div className="next-actions">
<button type="button" className="transparent" onClick={() => navigate("/new/verify")}>
<FormattedMessage {...messages.Skip} />

View File

@ -1,18 +1,33 @@
.new-user p {
.new-user {
color: var(--font-secondary-color);
}
.new-user li {
color: var(--font-secondary-color);
.new-user input {
font-size: 16px;
}
.new-user p {
font-weight: 400;
font-size: 16px;
line-height: 24px;
}
.new-user p > a {
color: var(--highlight);
}
.new-user li {
line-height: 24px;
}
.new-user li > a {
color: var(--highlight);
}
.new-user .nip-handle {
max-width: 120px;
}
.new-user h1 {
color: var(--font-color);
font-weight: 700;
@ -21,6 +36,8 @@
}
.new-user h2 {
margin-top: 24px;
margin-bottom: 16px;
color: var(--font-color);
font-weight: 600;
font-size: 16px;
@ -84,6 +101,10 @@
margin-right: 12px;
}
.new-user .next-actions.continue-actions {
margin-bottom: 0;
}
.new-user > .copy {
padding: 12px 16px;
border: 2px dashed #222222;
@ -149,3 +170,10 @@
.new-user .nip-container input[type="text"] {
width: 166px;
}
.new-user .help-text {
margin-top: 6px;
font-weight: 400;
font-size: 14px;
line-height: 24px;
}

View File

@ -43,12 +43,10 @@ html.light {
--bg-color: #f8f8f8;
--font-color: #27272a;
--font-secondary-color: #71717a;
--font-tertiary-color: #71717a;
--font-tertiary-color: #52525b;
--border-color: rgba(167, 167, 167, 0.3);
--highlight-light: #16aac1;
--highlight: #0284c7;
--highlight-dark: #0a52b5;
--highlight: #7139f1;
--modal-bg-color: rgba(240, 240, 240, 0.8);
--note-bg: white;
@ -127,6 +125,7 @@ button {
padding: 6px 12px;
font-weight: 600;
color: white;
min-height: 35px;
font-size: var(--font-size);
background-color: var(--highlight);
border: none;
@ -277,7 +276,7 @@ textarea {
color: var(--font-color);
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
border-radius: 12px;
outline: none;
}