Preferences page

This commit is contained in:
Kieran 2023-08-21 14:58:57 +01:00
parent 35423cc91b
commit 976f841d0b
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
45 changed files with 484 additions and 467 deletions

View File

@ -292,6 +292,12 @@
<path d="M8.70711 3.70711C9.09763 3.31658 9.09763 2.68342 8.70711 2.29289C8.31658 1.90237 7.68342 1.90237 7.29289 2.29289L3.29289 6.29289C2.90237 6.68342 2.90237 7.31658 3.29289 7.70711L7.29289 11.7071C7.68342 12.0976 8.31658 12.0976 8.70711 11.7071C9.09763 11.3166 9.09763 10.6834 8.70711 10.2929L6.41421 8L14 8C16.7614 8 19 10.2386 19 13C19 15.7614 16.7614 18 14 18H4C3.44772 18 3 18.4477 3 19C3 19.5523 3.44772 20 4 20H14C17.866 20 21 16.866 21 13C21 9.13401 17.866 6 14 6L6.41421 6L8.70711 3.70711Z" fill="currentColor"/> <path d="M8.70711 3.70711C9.09763 3.31658 9.09763 2.68342 8.70711 2.29289C8.31658 1.90237 7.68342 1.90237 7.29289 2.29289L3.29289 6.29289C2.90237 6.68342 2.90237 7.31658 3.29289 7.70711L7.29289 11.7071C7.68342 12.0976 8.31658 12.0976 8.70711 11.7071C9.09763 11.3166 9.09763 10.6834 8.70711 10.2929L6.41421 8L14 8C16.7614 8 19 10.2386 19 13C19 15.7614 16.7614 18 14 18H4C3.44772 18 3 18.4477 3 19C3 19.5523 3.44772 20 4 20H14C17.866 20 21 16.866 21 13C21 9.13401 17.866 6 14 6L6.41421 6L8.70711 3.70711Z" fill="currentColor"/>
</g> </g>
</symbol> </symbol>
<symbol id="upload-01" viewBox="0 0 16 16" fill="none">
<g>
<path d="M7.52876 1.52925C7.78911 1.2689 8.21122 1.2689 8.47157 1.52925L11.8049 4.86258C12.0652 5.12293 12.0652 5.54504 11.8049 5.80539C11.5446 6.06574 11.1224 6.06574 10.8621 5.80539L8.66683 3.61013L8.66683 10.0007C8.66683 10.3688 8.36835 10.6673 8.00016 10.6673C7.63197 10.6673 7.3335 10.3688 7.3335 10.0007L7.3335 3.61013L5.13823 5.80539C4.87788 6.06574 4.45577 6.06574 4.19543 5.80539C3.93508 5.54504 3.93508 5.12293 4.19543 4.86258L7.52876 1.52925Z" fill="currentColor"/>
<path d="M2.00016 9.33398C2.36835 9.33398 2.66683 9.63246 2.66683 10.0007V10.8007C2.66683 11.3717 2.66735 11.7599 2.69186 12.06C2.71574 12.3522 2.75903 12.5017 2.81215 12.606C2.93999 12.8569 3.14396 13.0608 3.39484 13.1887C3.49911 13.2418 3.64858 13.2851 3.94086 13.3089C4.24091 13.3335 4.62911 13.334 5.20016 13.334H10.8002C11.3712 13.334 11.7594 13.3335 12.0595 13.3089C12.3517 13.2851 12.5012 13.2418 12.6055 13.1887C12.8564 13.0608 13.0603 12.8569 13.1882 12.606C13.2413 12.5017 13.2846 12.3522 13.3085 12.06C13.333 11.7599 13.3335 11.3717 13.3335 10.8007V10.0007C13.3335 9.63246 13.632 9.33398 14.0002 9.33398C14.3684 9.33398 14.6668 9.63246 14.6668 10.0007V10.8282C14.6668 11.3648 14.6668 11.8077 14.6374 12.1685C14.6067 12.5433 14.541 12.8877 14.3762 13.2113C14.1205 13.7131 13.7126 14.121 13.2108 14.3767C12.8872 14.5415 12.5428 14.6072 12.168 14.6379C11.8073 14.6673 11.3643 14.6673 10.8277 14.6673H5.17263C4.63598 14.6673 4.19308 14.6673 3.83228 14.6379C3.45755 14.6072 3.11308 14.5415 2.78952 14.3767C2.28776 14.121 1.87981 13.7131 1.62415 13.2113C1.45929 12.8877 1.39358 12.5433 1.36296 12.1685C1.33348 11.8077 1.33349 11.3648 1.3335 10.8282V10.0007C1.3335 9.63246 1.63197 9.33398 2.00016 9.33398Z" fill="currentColor"/>
</g>
</symbol>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -11,19 +11,21 @@ interface AvatarProps {
user?: UserMetadata; user?: UserMetadata;
onClick?: () => void; onClick?: () => void;
size?: number; size?: number;
image?: string;
} }
const Avatar = ({ user, size, onClick }: AvatarProps) => { const Avatar = ({ user, size, onClick, image }: AvatarProps) => {
const [url, setUrl] = useState<string>(Nostrich); const [url, setUrl] = useState<string>(Nostrich);
const { proxy } = useImgProxy(); const { proxy } = useImgProxy();
useEffect(() => { useEffect(() => {
if (user?.picture) { const url = image ?? user?.picture;
const url = proxy(user.picture, size ?? 120); if (url) {
setUrl(url); const proxyUrl = proxy(url, size ?? 120);
setUrl(proxyUrl);
} else { } else {
setUrl(Nostrich); setUrl(Nostrich);
} }
}, [user]); }, [user, image]);
const backgroundImage = `url(${url})`; const backgroundImage = `url(${url})`;
const style = { "--img-url": backgroundImage } as CSSProperties; const style = { "--img-url": backgroundImage } as CSSProperties;

View File

@ -0,0 +1,20 @@
.avatar .edit,
.banner .edit {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background-color: var(--bg-color);
cursor: pointer;
opacity: 0;
border-radius: 100%;
}
.avatar .edit.new {
opacity: 0.5;
}
.avatar .edit:hover {
opacity: 0.5;
}

View File

@ -1,7 +1,9 @@
import "./AvatarEditor.css";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";
import { useState } from "react"; import { useState } from "react";
import useFileUpload from "Upload"; import useFileUpload from "Upload";
import { openFile, unwrap } from "SnortUtils"; import { openFile, unwrap } from "SnortUtils";
import Spinner from "Icons/Spinner";
interface AvatarEditorProps { interface AvatarEditorProps {
picture?: string; picture?: string;
@ -11,9 +13,11 @@ interface AvatarEditorProps {
export default function AvatarEditor({ picture, onPictureChange }: AvatarEditorProps) { export default function AvatarEditor({ picture, onPictureChange }: AvatarEditorProps) {
const uploader = useFileUpload(); const uploader = useFileUpload();
const [error, setError] = useState(""); const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
async function uploadFile() { async function uploadFile() {
setError(""); setError("");
setLoading(true);
try { try {
const f = await openFile(); const f = await openFile();
if (f) { if (f) {
@ -32,6 +36,7 @@ export default function AvatarEditor({ picture, onPictureChange }: AvatarEditorP
setError(`Upload failed`); setError(`Upload failed`);
} }
} }
setLoading(false);
} }
return ( return (
@ -39,7 +44,7 @@ export default function AvatarEditor({ picture, onPictureChange }: AvatarEditorP
<div className="flex f-center"> <div className="flex f-center">
<div style={{ backgroundImage: `url(${picture})` }} className="avatar"> <div style={{ backgroundImage: `url(${picture})` }} className="avatar">
<div className={`edit${picture ? "" : " new"}`} onClick={() => uploadFile().catch(console.error)}> <div className={`edit${picture ? "" : " new"}`} onClick={() => uploadFile().catch(console.error)}>
<Icon name={picture ? "edit" : "camera-plus"} /> {loading ? <Spinner /> : <Icon name={picture ? "edit" : "camera-plus"} />}
</div> </div>
</div> </div>
</div> </div>

View File

@ -41,6 +41,8 @@ export function updatePreferences(state: LoginSession, p: UserPreferences) {
export function logout(k: HexKey) { export function logout(k: HexKey) {
LoginStore.removeSession(k); LoginStore.removeSession(k);
//TODO: delete giftwarps for:k
//TODO: delete notifications for:k
} }
export function markNotificationsRead(state: LoginSession) { export function markNotificationsRead(state: LoginSession) {

View File

@ -158,6 +158,7 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
const pk = unwrap(s.publicKey); const pk = unwrap(s.publicKey);
if (this.#accounts.has(pk)) { if (this.#accounts.has(pk)) {
this.#accounts.set(pk, s); this.#accounts.set(pk, s);
console.debug("SET SESSION", s);
this.#save(); this.#save();
} }
} }
@ -175,7 +176,7 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
const s = this.#activeAccount ? this.#accounts.get(this.#activeAccount) : undefined; const s = this.#activeAccount ? this.#accounts.get(this.#activeAccount) : undefined;
if (!s) return LoggedOut; if (!s) return LoggedOut;
return s; return { ...s };
} }
#createPublisher(l: LoginSession) { #createPublisher(l: LoginSession) {

View File

@ -74,7 +74,7 @@ const DonatePage = () => {
} }
return ( return (
<div className="main-content m5"> <div className="main-content p">
<h2> <h2>
<FormattedMessage defaultMessage="Help fund the development of Snort" /> <FormattedMessage defaultMessage="Help fund the development of Snort" />
</h2> </h2>

View File

@ -5,8 +5,6 @@ import Nip5Service from "Element/Nip5Service";
import messages from "./messages"; import messages from "./messages";
import "./Verification.css";
export const SnortNostrAddressService = { export const SnortNostrAddressService = {
name: "Snort", name: "Snort",
service: `${ApiHost}/api/v1/n5sp`, service: `${ApiHost}/api/v1/n5sp`,
@ -26,11 +24,11 @@ export const Nip5Services = [
}, },
]; ];
export default function VerificationPage() { export default function NostrAddressPage() {
return ( return (
<div className="main-content verification"> <div className="main-content p">
<h2> <h2>
<FormattedMessage {...messages.GetVerified} /> <FormattedMessage defaultMessage="Buy nostr address" />
</h2> </h2>
<p> <p>
<FormattedMessage {...messages.Nip05} /> <FormattedMessage {...messages.Nip05} />

View File

@ -86,7 +86,7 @@ export default function NotificationsPage() {
const timeGrouped = useMemo(() => { const timeGrouped = useMemo(() => {
return orderDescending([...notifications]) return orderDescending([...notifications])
.filter(a => !isMuted(a.pubkey)) .filter(a => !isMuted(a.pubkey) && findTag(a, "p") === login.publicKey)
.reduce((acc, v) => { .reduce((acc, v) => {
const key = `${timeKey(v)}:${notificationContext(v as TaggedRawEvent)?.encode()}:${v.kind}`; const key = `${timeKey(v)}:${notificationContext(v as TaggedRawEvent)?.encode()}:${v.kind}`;
if (acc.has(key)) { if (acc.has(key)) {

View File

@ -333,11 +333,19 @@ export const RootRoutes = [
}, },
{ {
path: "trending/people", path: "trending/people",
element: <TrendingUsers />, element: (
<div className="p">
<TrendingUsers />
</div>
),
}, },
{ {
path: "suggested", path: "suggested",
element: <SuggestedProfiles />, element: (
<div className="p">
<SuggestedProfiles />
</div>
),
}, },
{ {
path: "/t/:tag", path: "/t/:tag",

View File

@ -29,31 +29,33 @@ export const SettingsRoutes: RouteObject[] = [
{ {
path: "", path: "",
element: <SettingsIndex />, element: <SettingsIndex />,
children: [
{
path: "profile",
element: <Profile />,
},
{
path: "relays",
element: <Relay />,
},
{
path: "relays/:id",
element: <RelayInfo />,
},
{
path: "preferences",
element: <Preferences />,
},
{
path: "accounts",
element: <AccountsPage />,
},
{
path: "keys",
element: <ExportKeys />,
},
...ManageHandleRoutes,
...WalletSettingsRoutes,
],
}, },
{
path: "profile",
element: <Profile />,
},
{
path: "relays",
element: <Relay />,
},
{
path: "relays/:id",
element: <RelayInfo />,
},
{
path: "preferences",
element: <Preferences />,
},
{
path: "accounts",
element: <AccountsPage />,
},
{
path: "keys",
element: <ExportKeys />,
},
...ManageHandleRoutes,
...WalletSettingsRoutes,
]; ];

View File

@ -1,3 +0,0 @@
.verification a {
color: var(--highlight);
}

View File

@ -204,15 +204,11 @@ export default function WalletPage() {
} }
return ( return (
<div className="main-content"> <div className="main-content p">
{error && <b className="error">{error}</b>} {error && <b className="error">{error}</b>}
{walletList()} {walletList()}
{unlockWallet()} {unlockWallet()}
{walletInfo()} {walletInfo()}
<button onClick={() => Wallets.remove(unwrap(walletState.config).id)}>
<FormattedMessage defaultMessage="Delete Account" />
</button>
</div> </div>
); );
} }

View File

@ -1,3 +1,7 @@
.zap-pool input[type="range"] { .zap-pool input[type="range"] {
width: 200px; width: 200px;
} }
.zap-pool h4 {
margin: 0;
}

View File

@ -92,7 +92,7 @@ export default function ZapPoolPage() {
const sumPending = zapPool.reduce((acc, v) => acc + v.sum, 0); const sumPending = zapPool.reduce((acc, v) => acc + v.sum, 0);
return ( return (
<div className="zap-pool main-content"> <div className="zap-pool main-content p">
<h1> <h1>
<FormattedMessage defaultMessage="Zap Pool" /> <FormattedMessage defaultMessage="Zap Pool" />
</h1> </h1>
@ -150,7 +150,7 @@ export default function ZapPoolPage() {
</AsyncButton> </AsyncButton>
)} )}
</p> </p>
<div className="card"> <div>
<ZapTarget <ZapTarget
target={ target={
zapPool.find(b => b.pubkey === bech32ToHex(SnortPubKey) && b.type === ZapPoolRecipientType.Generic) ?? { zapPool.find(b => b.pubkey === bech32ToHex(SnortPubKey) && b.type === ZapPoolRecipientType.Generic) ?? {
@ -166,7 +166,7 @@ export default function ZapPoolPage() {
<FormattedMessage defaultMessage="Relays" /> <FormattedMessage defaultMessage="Relays" />
</h3> </h3>
{relayConnections.map(a => ( {relayConnections.map(a => (
<div className="card"> <div>
<h4>{getRelayName(a.address)}</h4> <h4>{getRelayName(a.address)}</h4>
<ZapTarget <ZapTarget
target={ target={
@ -184,7 +184,7 @@ export default function ZapPoolPage() {
<FormattedMessage defaultMessage="File hosts" /> <FormattedMessage defaultMessage="File hosts" />
</h3> </h3>
{UploaderServices.map(a => ( {UploaderServices.map(a => (
<div className="card"> <div>
<h4>{a.name}</h4> <h4>{a.name}</h4>
<ZapTarget <ZapTarget
target={ target={
@ -202,7 +202,7 @@ export default function ZapPoolPage() {
<FormattedMessage defaultMessage="Data Providers" /> <FormattedMessage defaultMessage="Data Providers" />
</h3> </h3>
{DataProviders.map(a => ( {DataProviders.map(a => (
<div className="card"> <div>
<h4>{a.name}</h4> <h4>{a.name}</h4>
<ZapTarget <ZapTarget
target={ target={

View File

@ -25,7 +25,7 @@ export default function DiscoverFollows() {
} }
return ( return (
<div className="main-content new-user" dir="auto"> <div className="main-content new-user p" dir="auto">
<Logo /> <Logo />
<div className="progress-bar"> <div className="progress-bar">
<div className="progress"></div> <div className="progress"></div>
@ -45,6 +45,9 @@ export default function DiscoverFollows() {
<FormattedMessage {...messages.PopularAccounts} /> <FormattedMessage {...messages.PopularAccounts} />
</h3> </h3>
{sortedReccomends.length > 0 && <FollowListBase pubkeys={sortedReccomends} showAbout={true} />} {sortedReccomends.length > 0 && <FollowListBase pubkeys={sortedReccomends} showAbout={true} />}
<h3>
<FormattedMessage defaultMessage="Trending Users" />
</h3>
<TrendingUsers /> <TrendingUsers />
</div> </div>
); );

View File

@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom";
import { useUserProfile } from "@snort/system-react"; import { useUserProfile } from "@snort/system-react";
import Logo from "Element/Logo"; import Logo from "Element/Logo";
import { Nip5Services } from "Pages/Verification"; import { Nip5Services } from "Pages/NostrAddressPage";
import Nip5Service from "Element/Nip5Service"; import Nip5Service from "Element/Nip5Service";
import ProfileImage from "Element/ProfileImage"; import ProfileImage from "Element/ProfileImage";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";

View File

@ -90,7 +90,7 @@ export default function NewUserFlow() {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<div className="main-content new-user" dir="auto"> <div className="main-content new-user p" dir="auto">
<Logo /> <Logo />
<div className="progress-bar"> <div className="progress-bar">
<div className="progress progress-first"></div> <div className="progress progress-first"></div>
@ -98,31 +98,25 @@ export default function NewUserFlow() {
<h1> <h1>
<FormattedMessage {...messages.SaveKeys} /> <FormattedMessage {...messages.SaveKeys} />
</h1> </h1>
<div className="card flex"> <div className="flex f-space">
<div className="flex f-col f-grow"> <FormattedMessage defaultMessage="Language" />
<div> <select
<FormattedMessage defaultMessage="Language" /> value={login.preferences.language || DefaultPreferences.language}
</div> onChange={e =>
</div> updatePreferences(login, {
<div> ...login.preferences,
<select language: e.target.value,
value={login.preferences.language || DefaultPreferences.language} })
onChange={e => }
updatePreferences(login, { style={{ textTransform: "capitalize" }}>
...login.preferences, {AllLanguageCodes.sort().map(a => (
language: e.target.value, <option value={a}>
}) {new Intl.DisplayNames([a], {
} type: "language",
style={{ textTransform: "capitalize" }}> }).of(a)}
{AllLanguageCodes.sort().map(a => ( </option>
<option value={a}> ))}
{new Intl.DisplayNames([a], { </select>
type: "language",
}).of(a)}
</option>
))}
</select>
</div>
</div> </div>
<p> <p>
<FormattedMessage {...messages.SaveKeysHelp} /> <FormattedMessage {...messages.SaveKeysHelp} />

View File

@ -47,7 +47,7 @@ export default function ProfileSetup() {
}; };
return ( return (
<div className="main-content new-user" dir="auto"> <div className="main-content new-user p" dir="auto">
<Logo /> <Logo />
<div className="progress-bar"> <div className="progress-bar">
<div className="progress progress-second"></div> <div className="progress progress-second"></div>

View File

@ -1,4 +1,4 @@
.export-keys > .copy { .copy.dashed {
padding: 12px 16px; padding: 12px 16px;
border: 2px dashed #222222; border: 2px dashed #222222;
border-radius: 16px; border-radius: 16px;

View File

@ -10,18 +10,18 @@ import { hexToBech32 } from "SnortUtils";
export default function ExportKeys() { export default function ExportKeys() {
const { publicKey, privateKey, generatedEntropy } = useLogin(); const { publicKey, privateKey, generatedEntropy } = useLogin();
return ( return (
<div className="export-keys"> <div className="flex-column g12">
<h3> <h3>
<FormattedMessage defaultMessage="Public Key" /> <FormattedMessage defaultMessage="Public Key" />
</h3> </h3>
<Copy text={hexToBech32("npub", publicKey ?? "")} maxSize={48} className="mb10" /> <Copy text={hexToBech32("npub", publicKey ?? "")} className="dashed" />
<Copy text={encodeTLV(NostrPrefix.Profile, publicKey ?? "")} maxSize={48} /> <Copy text={encodeTLV(NostrPrefix.Profile, publicKey ?? "")} className="dashed" />
{privateKey && ( {privateKey && (
<> <>
<h3> <h3>
<FormattedMessage defaultMessage="Private Key" /> <FormattedMessage defaultMessage="Private Key" />
</h3> </h3>
<Copy text={hexToBech32("nsec", privateKey)} maxSize={48} /> <Copy text={hexToBech32("nsec", privateKey)} className="dashed" />
</> </>
)} )}
{generatedEntropy && ( {generatedEntropy && (
@ -29,7 +29,7 @@ export default function ExportKeys() {
<h3> <h3>
<FormattedMessage defaultMessage="Mnemonic" /> <FormattedMessage defaultMessage="Mnemonic" />
</h3> </h3>
<Copy text={hexToMnemonic(generatedEntropy ?? "")} maxSize={48} /> <Copy text={hexToMnemonic(generatedEntropy ?? "")} className="dashed" />
</> </>
)} )}
</div> </div>

View File

@ -1,8 +1,14 @@
.preferences small { .preferences small {
margin-top: 0.5em;
color: var(--font-secondary-color); color: var(--font-secondary-color);
} }
.preferences select { .preferences select {
min-width: 100px; min-width: 100px;
} }
.preferences h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
line-height: 22px; /* 137.5% */
}

View File

@ -32,6 +32,7 @@ export const AllLanguageCodes = [
const PreferencesPage = () => { const PreferencesPage = () => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const login = useLogin(); const login = useLogin();
console.debug(login);
const perf = login.preferences; const perf = login.preferences;
const [emoji, setEmoji] = useState<Array<{ name: string; char: string }>>([]); const [emoji, setEmoji] = useState<Array<{ name: string; char: string }>>([]);
@ -42,17 +43,15 @@ const PreferencesPage = () => {
}, []); }, []);
return ( return (
<div className="preferences"> <div className="preferences flex-column g24">
<h3> <h3>
<FormattedMessage {...messages.Preferences} /> <FormattedMessage {...messages.Preferences} />
</h3> </h3>
<div className="card flex"> <div className="flex f-space w-max">
<div className="flex f-col f-grow"> <h4>
<div> <FormattedMessage defaultMessage="Language" />
<FormattedMessage defaultMessage="Language" /> </h4>
</div>
</div>
<div> <div>
<select <select
value={perf.language || DefaultPreferences.language} value={perf.language || DefaultPreferences.language}
@ -73,12 +72,10 @@ const PreferencesPage = () => {
</select> </select>
</div> </div>
</div> </div>
<div className="card flex"> <div className="flex f-space w-max">
<div className="flex f-col f-grow"> <h4>
<div> <FormattedMessage {...messages.Theme} />
<FormattedMessage {...messages.Theme} /> </h4>
</div>
</div>
<div> <div>
<select <select
value={perf.theme} value={perf.theme}
@ -100,12 +97,10 @@ const PreferencesPage = () => {
</select> </select>
</div> </div>
</div> </div>
<div className="card flex"> <div className="flex f-space w-max">
<div className="flex f-col f-grow"> <h4>
<div> <FormattedMessage {...messages.DefaultRootTab} />
<FormattedMessage {...messages.DefaultRootTab} /> </h4>
</div>
</div>
<div> <div>
<select <select
value={perf.defaultRootTab} value={perf.defaultRootTab}
@ -127,16 +122,17 @@ const PreferencesPage = () => {
</select> </select>
</div> </div>
</div> </div>
<div className="card flex"> <div className="flex w-max">
<div className="flex f-col f-grow"> <div className="flex-column g8">
<div> <h4>
<FormattedMessage {...messages.AutoloadMedia} /> <FormattedMessage {...messages.AutoloadMedia} />
</div> </h4>
<small> <small>
<FormattedMessage {...messages.AutoloadMediaHelp} /> <FormattedMessage {...messages.AutoloadMediaHelp} />
</small> </small>
<div className="mt10"> <div className="w-max">
<select <select
className="w-max"
value={perf.autoLoadMedia} value={perf.autoLoadMedia}
onChange={e => onChange={e =>
updatePreferences(login, { updatePreferences(login, {
@ -157,11 +153,11 @@ const PreferencesPage = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="card flex"> <div className="flex f-space w-max">
<div className="flex f-col f-grow"> <div className="flex-column g8">
<div> <h4>
<FormattedMessage defaultMessage="Proof of Work" /> <FormattedMessage defaultMessage="Proof of Work" />
</div> </h4>
<small> <small>
<FormattedMessage defaultMessage="Amount of work to apply to all published events" /> <FormattedMessage defaultMessage="Amount of work to apply to all published events" />
</small> </small>
@ -175,12 +171,10 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="card flex"> <div className="flex f-space w-max">
<div className="flex f-col f-grow"> <h4>
<div> <FormattedMessage defaultMessage="Default Zap amount" />
<FormattedMessage defaultMessage="Default Zap amount" /> </h4>
</div>
</div>
<div> <div>
<input <input
type="number" type="number"
@ -190,11 +184,11 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="card flex"> <div className="flex f-space w-max">
<div className="flex f-col f-grow"> <div className="flex-column g8">
<div> <h4>
<FormattedMessage defaultMessage="Auto Zap" /> <FormattedMessage defaultMessage="Auto Zap" />
</div> </h4>
<small> <small>
<FormattedMessage defaultMessage="Automatically zap every note when loaded" /> <FormattedMessage defaultMessage="Automatically zap every note when loaded" />
</small> </small>
@ -207,12 +201,12 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="card flex f-col"> <div className="flex-column">
<div className="flex w-max"> <div className="flex f-space">
<div className="flex f-col f-grow"> <div className="flex-column g8">
<div> <h4>
<FormattedMessage {...messages.ImgProxy} /> <FormattedMessage {...messages.ImgProxy} />
</div> </h4>
<small> <small>
<FormattedMessage {...messages.ImgProxyHelp} /> <FormattedMessage {...messages.ImgProxyHelp} />
</small> </small>
@ -231,7 +225,7 @@ const PreferencesPage = () => {
</div> </div>
</div> </div>
{perf.imgProxyConfig && ( {perf.imgProxyConfig && (
<div className="w-max mt10 form"> <div className="w-max form">
<div className="form-group"> <div className="form-group">
<div> <div>
<FormattedMessage {...messages.ServiceUrl} /> <FormattedMessage {...messages.ServiceUrl} />
@ -307,11 +301,11 @@ const PreferencesPage = () => {
</div> </div>
)} )}
</div> </div>
<div className="card flex"> <div className="flex f-space w-max">
<div className="flex f-col f-grow"> <div className="flex-column g8">
<div> <h4>
<FormattedMessage {...messages.EnableReactions} /> <FormattedMessage {...messages.EnableReactions} />
</div> </h4>
<small> <small>
<FormattedMessage {...messages.EnableReactionsHelp} /> <FormattedMessage {...messages.EnableReactionsHelp} />
</small> </small>
@ -324,43 +318,39 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="card flex"> <div className="flex-column g8">
<div className="flex f-col f-grow"> <h4>
<div> <FormattedMessage {...messages.ReactionEmoji} />
<FormattedMessage {...messages.ReactionEmoji} /> </h4>
</div> <small>
<small> <FormattedMessage {...messages.ReactionEmojiHelp} />
<FormattedMessage {...messages.ReactionEmojiHelp} /> </small>
</small> <select
<div className="mt10"> className="emoji-selector"
<select value={perf.reactionEmoji}
className="emoji-selector" onChange={e =>
value={perf.reactionEmoji} updatePreferences(login, {
onChange={e => ...perf,
updatePreferences(login, { reactionEmoji: e.target.value,
...perf, })
reactionEmoji: e.target.value, }>
}) <option value="+">
}> + <FormattedMessage {...messages.Default} />
<option value="+"> </option>
+ <FormattedMessage {...messages.Default} /> {emoji.map(({ name, char }) => {
return (
<option value={char}>
{name} {char}
</option> </option>
{emoji.map(({ name, char }) => { );
return ( })}
<option value={char}> </select>
{name} {char}
</option>
);
})}
</select>
</div>
</div>
</div> </div>
<div className="card flex"> <div className="flex f-space">
<div className="flex f-col f-grow"> <div className="flex-column g8">
<div> <h4>
<FormattedMessage {...messages.ConfirmReposts} /> <FormattedMessage {...messages.ConfirmReposts} />
</div> </h4>
<small> <small>
<FormattedMessage {...messages.ConfirmRepostsHelp} /> <FormattedMessage {...messages.ConfirmRepostsHelp} />
</small> </small>
@ -373,11 +363,11 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="card flex"> <div className="flex f-space">
<div className="flex f-col f-grow"> <div className="flex-column g8">
<div> <h4>
<FormattedMessage {...messages.ShowLatest} /> <FormattedMessage {...messages.ShowLatest} />
</div> </h4>
<small> <small>
<FormattedMessage {...messages.ShowLatestHelp} /> <FormattedMessage {...messages.ShowLatestHelp} />
</small> </small>
@ -390,37 +380,33 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="card flex"> <div className="flex-column g8">
<div className="flex f-col f-grow"> <h4>
<div> <FormattedMessage {...messages.FileUpload} />
<FormattedMessage {...messages.FileUpload} /> </h4>
</div> <small>
<small> <FormattedMessage {...messages.FileUploadHelp} />
<FormattedMessage {...messages.FileUploadHelp} /> </small>
</small> <select
<div className="mt10"> value={perf.fileUploader}
<select onChange={e =>
value={perf.fileUploader} updatePreferences(login, {
onChange={e => ...perf,
updatePreferences(login, { fileUploader: e.target.value,
...perf, } as UserPreferences)
fileUploader: e.target.value, }>
} as UserPreferences) <option value="void.cat">
}> void.cat <FormattedMessage {...messages.Default} />
<option value="void.cat"> </option>
void.cat <FormattedMessage {...messages.Default} /> <option value="nostr.build">nostr.build</option>
</option> <option value="nostrimg.com">nostrimg.com</option>
<option value="nostr.build">nostr.build</option> </select>
<option value="nostrimg.com">nostrimg.com</option>
</select>
</div>
</div>
</div> </div>
<div className="card flex"> <div className="flex f-space">
<div className="flex f-col f-grow"> <div className="flex-column g8">
<div> <h4>
<FormattedMessage {...messages.DebugMenus} /> <FormattedMessage {...messages.DebugMenus} />
</div> </h4>
<small> <small>
<FormattedMessage {...messages.DebugMenusHelp} /> <FormattedMessage {...messages.DebugMenusHelp} />
</small> </small>

View File

@ -1,53 +1,51 @@
.settings h4 {
font-size: 16px;
font-weight: 600;
margin: 0;
}
.settings .avatar { .settings .avatar {
width: 256px; width: 64px;
height: 256px; height: 64px;
background-size: cover; background-size: cover;
border-radius: 100%; border-radius: 100%;
cursor: pointer;
margin-bottom: 20px;
} }
.settings .banner { .settings .banner {
width: 300px; width: 100%;
height: 150px; height: 100%;
background-size: cover; background-size: cover;
margin-bottom: 20px; border-radius: 12px;
} }
.settings .image-settings { .settings .image-settings {
display: block; position: relative;
align-items: center;
justify-content: center;
}
.settings .image-setting {
display: flex;
justify-content: space-between;
}
.settings .image-setting > div:first-child {
align-self: center;
}
.avatar .edit,
.banner .edit {
display: flex;
align-items: center;
justify-content: center;
width: 100%; width: 100%;
height: 100%; height: 145px;
background-color: var(--bg-color); margin-block-end: 45px;
cursor: pointer;
opacity: 0;
border-radius: 100%;
} }
.avatar .edit.new { .settings .image-settings > div {
opacity: 0.5; position: absolute;
} }
.avatar .edit:hover { .settings .image-settings .avatar-stack {
opacity: 0.5; bottom: -32px;
}
.settings .image-settings .avatar-stack .btn-rnd {
position: absolute;
padding: 0;
width: 32px;
height: 32px;
right: -10px;
bottom: -10px;
}
.settings .image-settings .banner button {
right: 16px;
top: 12px;
position: absolute;
} }
.settings .editor textarea { .settings .editor textarea {
@ -59,3 +57,8 @@
.settings .actions { .settings .actions {
margin-top: 16px; margin-top: 16px;
} }
.settings small {
font-size: 14px;
color: var(--font-secondary-color);
}

View File

@ -13,10 +13,8 @@ import useFileUpload from "Upload";
import AsyncButton from "Element/AsyncButton"; import AsyncButton from "Element/AsyncButton";
import { UserCache } from "Cache"; import { UserCache } from "Cache";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import AvatarEditor from "Element/AvatarEditor";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";
import Avatar from "Element/Avatar";
import messages from "./messages";
export interface ProfileSettingsProps { export interface ProfileSettingsProps {
avatar?: boolean; avatar?: boolean;
@ -31,7 +29,6 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
const uploader = useFileUpload(); const uploader = useFileUpload();
const [name, setName] = useState<string>(); const [name, setName] = useState<string>();
const [displayName, setDisplayName] = useState<string>();
const [picture, setPicture] = useState<string>(); const [picture, setPicture] = useState<string>();
const [banner, setBanner] = useState<string>(); const [banner, setBanner] = useState<string>();
const [about, setAbout] = useState<string>(); const [about, setAbout] = useState<string>();
@ -39,12 +36,9 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
const [nip05, setNip05] = useState<string>(); const [nip05, setNip05] = useState<string>();
const [lud16, setLud16] = useState<string>(); const [lud16, setLud16] = useState<string>();
const avatarPicture = (picture?.length ?? 0) === 0 ? Nostrich : picture;
useEffect(() => { useEffect(() => {
if (user) { if (user) {
setName(user.name); setName(user.name);
setDisplayName(user.display_name);
setPicture(user.picture); setPicture(user.picture);
setBanner(user.banner); setBanner(user.banner);
setAbout(user.about); setAbout(user.about);
@ -59,7 +53,6 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
const userCopy = { const userCopy = {
...user, ...user,
name, name,
display_name: displayName,
about, about,
picture, picture,
banner, banner,
@ -107,69 +100,62 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
} }
} }
async function setNewAvatar() {
const rsp = await uploadFile();
if (rsp) {
setPicture(rsp);
}
}
function editor() { function editor() {
return ( return (
<div className="editor form"> <div className="flex f-col g24">
<div className="form-group card"> <div className="flex f-col w-max g8">
<div> <h4>
<FormattedMessage {...messages.Name} />: <FormattedMessage defaultMessage="Name" />
</div> </h4>
<div> <input className="w-max" type="text" value={name} onChange={e => setName(e.target.value)} />
<input type="text" value={name} onChange={e => setName(e.target.value)} /> </div>
<div className="flex f-col w-max g8">
<h4>
<FormattedMessage defaultMessage="About" />
</h4>
<textarea className="w-max" onChange={e => setAbout(e.target.value)} value={about}></textarea>
</div>
<div className="flex f-col w-max g8">
<h4>
<FormattedMessage defaultMessage="Website" />
</h4>
<input className="w-max" type="text" value={website} onChange={e => setWebsite(e.target.value)} />
</div>
<div className="flex f-col w-max g8">
<h4>
<FormattedMessage defaultMessage="Nostr Address" />
</h4>
<div className="flex f-col g8 w-max">
<input type="text" className="w-max" value={nip05} onChange={e => setNip05(e.target.value)} />
<small>
<FormattedMessage defaultMessage="Usernames are not unique on Nostr. The nostr address is your unique human-readable address that is unique to you upon registration." />
</small>
<div className="flex g12">
<button className="flex f-center" type="button" onClick={() => navigate("/nostr-address")}>
<FormattedMessage defaultMessage="Buy nostr address" />
</button>
<button className="flex f-center secondary" type="button" onClick={() => navigate("/nostr-address")}>
<FormattedMessage defaultMessage="Get a free one" />
</button>
</div>
</div> </div>
</div> </div>
<div className="form-group card"> <div className="flex f-col w-max g8">
<div> <h4>
<FormattedMessage {...messages.DisplayName} />: <FormattedMessage defaultMessage="Lightning Address" />
</div> </h4>
<div> <input className="w-max" type="text" value={lud16} onChange={e => setLud16(e.target.value)} />
<input type="text" value={displayName} onChange={e => setDisplayName(e.target.value)} />
</div>
</div>
<div className="form-group card">
<div>
<FormattedMessage {...messages.About} />:
</div>
<div className="w-max">
<textarea className="w-max" onChange={e => setAbout(e.target.value)} value={about}></textarea>
</div>
</div>
<div className="form-group card">
<div>
<FormattedMessage {...messages.Website} />:
</div>
<div>
<input type="text" value={website} onChange={e => setWebsite(e.target.value)} />
</div>
</div>
<div className="form-group card">
<div>
<FormattedMessage {...messages.Nip05} />:
</div>
<div>
<input type="text" className="mr10" value={nip05} onChange={e => setNip05(e.target.value)} />
<button type="button" onClick={() => navigate("/verification")}>
<Icon name="shopping-bag" />
&nbsp; <FormattedMessage {...messages.Buy} />
</button>
</div>
</div>
<div className="form-group card">
<div>
<FormattedMessage {...messages.LnAddress} />:
</div>
<div>
<input type="text" value={lud16} onChange={e => setLud16(e.target.value)} />
</div>
</div>
<div className="form-group card">
<div></div>
<div>
<AsyncButton onClick={() => saveProfile()}>
<FormattedMessage {...messages.Save} />
</AsyncButton>
</div>
</div> </div>
<AsyncButton onClick={() => saveProfile()}>
<FormattedMessage defaultMessage="Save" />
</AsyncButton>
</div> </div>
); );
} }
@ -179,28 +165,23 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
return ( return (
<> <>
<div className="flex f-center image-settings"> <div className="flex f-center image-settings">
{(props.avatar ?? true) && ( {(props.banner ?? true) && (
<div className="image-setting card"> <div
<div> style={{
<FormattedMessage {...messages.Avatar} />: backgroundImage: `url(${(banner?.length ?? 0) === 0 ? Nostrich : banner})`,
</div> }}
<AvatarEditor picture={avatarPicture} onPictureChange={p => setPicture(p)} /> className="banner">
<AsyncButton type="button" onClick={() => setNewBanner()}>
<FormattedMessage defaultMessage="Upload" />
</AsyncButton>
</div> </div>
)} )}
{(props.banner ?? true) && ( {(props.avatar ?? true) && (
<div className="image-setting card"> <div className="avatar-stack">
<div> <Avatar user={user} image={picture} />
<FormattedMessage {...messages.Banner} />: <AsyncButton type="button" className="btn-rnd" onClick={() => setNewAvatar()}>
</div> <Icon name="upload-01" />
<div </AsyncButton>
style={{
backgroundImage: `url(${(banner?.length ?? 0) === 0 ? Nostrich : banner})`,
}}
className="banner">
<div className="edit" onClick={() => setNewBanner()}>
<FormattedMessage {...messages.Edit} />
</div>
</div>
</div> </div>
)} )}
</div> </div>
@ -209,12 +190,5 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
); );
} }
return ( return <div className="settings">{settings()}</div>;
<div className="settings">
<h3>
<FormattedMessage {...messages.EditProfile} />
</h3>
{settings()}
</div>
);
} }

View File

@ -22,7 +22,7 @@ const RelayInfo = () => {
<h3 className="pointer" onClick={() => navigate("/settings/relays")}> <h3 className="pointer" onClick={() => navigate("/settings/relays")}>
<FormattedMessage {...messages.Relays} /> <FormattedMessage {...messages.Relays} />
</h3> </h3>
<div className="card"> <div>
<h3>{stats?.info?.name}</h3> <h3>{stats?.info?.name}</h3>
<p>{stats?.info?.description}</p> <p>{stats?.info?.description}</p>

View File

@ -1,19 +1,29 @@
.settings-nav {
display: grid;
grid-template-columns: 237px auto;
}
.settings-nav > div {
border: 1px solid var(--gray-superdark);
}
.settings-nav > div:nth-child(2) {
padding: 12px 16px;
}
.settings-nav .card { .settings-nav .card {
cursor: pointer; cursor: pointer;
} }
.settings-row { .settings-row {
display: grid; display: grid;
grid-template-columns: 22px 1fr 8px; grid-template-columns: 24px 1fr 24px;
align-items: center; align-items: center;
font-weight: 600;
font-size: 16px;
padding: 0.8em 1em;
background: var(--note-bg);
border-radius: 10px;
cursor: pointer; cursor: pointer;
gap: 10px; padding: 12px 16px;
margin-bottom: 5px; gap: 8px;
font-size: 16px;
font-weight: 600;
} }
.settings-row.inner { .settings-row.inner {
@ -24,12 +34,12 @@
} }
.settings-group-header { .settings-group-header {
font-weight: 600; align-items: center;
cursor: pointer;
padding: 12px 16px;
gap: 8px;
font-size: 16px; font-size: 16px;
padding: 0.8em 1em; font-weight: 600;
background-color: var(--note-bg);
border-radius: 10px;
margin-bottom: 5px;
} }
.settings-row:hover, .settings-row:hover,
@ -41,11 +51,6 @@
margin-left: auto; margin-left: auto;
} }
.settings-row svg {
width: 100%;
height: 100%;
}
.settings-group-header .collapse-icon > svg { .settings-group-header .collapse-icon > svg {
width: 8px; width: 8px;
} }

View File

@ -1,12 +1,11 @@
import "./Root.css"; import "./Root.css";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import { useNavigate } from "react-router-dom"; import { Outlet, useNavigate } from "react-router-dom";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";
import { LoginStore, logout } from "Login"; import { LoginStore, logout } from "Login";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import { unwrap } from "SnortUtils"; import { unwrap } from "SnortUtils";
import { getCurrentSubscription } from "Subscription"; import { getCurrentSubscription } from "Subscription";
import { CollapsedSection } from "Element/Collapsed";
import messages from "./messages"; import messages from "./messages";
@ -21,80 +20,72 @@ const SettingsIndex = () => {
} }
return ( return (
<> <div className="settings-nav">
<div className="settings-nav"> <div>
<CollapsedSection <div className="settings-row" onClick={() => navigate("profile")}>
title={ <Icon name="profile" size={24} />
<div className="flex"> <FormattedMessage {...messages.Profile} />
<Icon name="user" className="mr10" /> <Icon name="arrowFront" size={16} />
<FormattedMessage defaultMessage="Account" /> </div>
</div> <div className="settings-row" onClick={() => navigate("relays")}>
} <Icon name="relay" size={24} />
className="settings-group-header"> <FormattedMessage {...messages.Relays} />
<div className="card"> <Icon name="arrowFront" size={16} />
<div className="settings-row inner" onClick={() => navigate("profile")}> </div>
<Icon name="profile" /> <div className="settings-row" onClick={() => navigate("keys")}>
<FormattedMessage {...messages.Profile} /> <Icon name="key" size={24} />
<Icon name="arrowFront" /> <FormattedMessage defaultMessage="Export Keys" />
</div> <Icon name="arrowFront" size={16} />
<div className="settings-row inner" onClick={() => navigate("relays")}> </div>
<Icon name="relay" /> <div className="settings-row" onClick={() => navigate("handle")}>
<FormattedMessage {...messages.Relays} /> <Icon name="badge" size={24} />
<Icon name="arrowFront" /> <FormattedMessage defaultMessage="Nostr Adddress" />
</div> <Icon name="arrowFront" size={16} />
<div className="settings-row inner" onClick={() => navigate("keys")}> </div>
<Icon name="key" /> <div className="settings-row" onClick={() => navigate("/subscribe/manage")}>
<FormattedMessage defaultMessage="Export Keys" /> <Icon name="diamond" size={24} />
<Icon name="arrowFront" /> <FormattedMessage defaultMessage="Subscription" />
</div> <Icon name="arrowFront" size={16} />
<div className="settings-row inner" onClick={() => navigate("handle")}> </div>
<Icon name="badge" /> {sub && (
<FormattedMessage defaultMessage="Nostr Adddress" /> <div className="settings-row" onClick={() => navigate("accounts")}>
<Icon name="arrowFront" /> <Icon name="code-circle" size={24} />
</div> <FormattedMessage defaultMessage="Account Switcher" />
<div className="settings-row inner" onClick={() => navigate("/subscribe/manage")}> <Icon name="arrowFront" size={16} />
<Icon name="diamond" />
<FormattedMessage defaultMessage="Subscription" />
<Icon name="arrowFront" />
</div>
{sub && (
<div className="settings-row inner" onClick={() => navigate("accounts")}>
<Icon name="code-circle" />
<FormattedMessage defaultMessage="Account Switcher" />
<Icon name="arrowFront" />
</div>
)}
</div> </div>
</CollapsedSection> )}
<div className="settings-row" onClick={() => navigate("preferences")}> <div className="settings-row" onClick={() => navigate("preferences")}>
<Icon name="gear" /> <Icon name="gear" size={24} />
<FormattedMessage {...messages.Preferences} /> <FormattedMessage {...messages.Preferences} />
<Icon name="arrowFront" /> <Icon name="arrowFront" size={16} />
</div> </div>
<div className="settings-row" onClick={() => navigate("wallet")}> <div className="settings-row" onClick={() => navigate("wallet")}>
<Icon name="wallet" /> <Icon name="wallet" size={24} />
<FormattedMessage defaultMessage="Wallet" /> <FormattedMessage defaultMessage="Wallet" />
<Icon name="arrowFront" /> <Icon name="arrowFront" size={16} />
</div> </div>
<div className="settings-row" onClick={() => navigate("/donate")}> <div className="settings-row" onClick={() => navigate("/donate")}>
<Icon name="heart" /> <Icon name="heart" size={24} />
<FormattedMessage {...messages.Donate} /> <FormattedMessage {...messages.Donate} />
<Icon name="arrowFront" /> <Icon name="arrowFront" size={16} />
</div> </div>
<div className="settings-row" onClick={() => navigate("/zap-pool")}> <div className="settings-row" onClick={() => navigate("/zap-pool")}>
<Icon name="piggy-bank" /> <Icon name="piggy-bank" size={24} />
<FormattedMessage defaultMessage="Zap Pool" /> <FormattedMessage defaultMessage="Zap Pool" />
<Icon name="arrowFront" /> <Icon name="arrowFront" size={16} />
</div> </div>
<div className="settings-row" onClick={handleLogout}> <div className="settings-row" onClick={handleLogout}>
<Icon name="logout" /> <Icon name="logout" size={24} />
<FormattedMessage {...messages.LogOut} /> <FormattedMessage {...messages.LogOut} />
<Icon name="arrowFront" /> <Icon name="arrowFront" size={16} />
</div> </div>
</div> </div>
</> <div>
<Outlet />
</div>
</div>
); );
}; };

View File

@ -5,10 +5,10 @@
grid-gap: 10px; grid-gap: 10px;
} }
.wallet-grid .card { .wallet-grid > div {
cursor: pointer; cursor: pointer;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-end;
align-items: center; align-items: center;
justify-content: space-between;
} }

View File

@ -1,7 +1,7 @@
import "./WalletSettings.css"; import "./WalletSettings.css";
import LndLogo from "lnd-logo.png"; import LndLogo from "lnd-logo.png";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import { RouteObject, useNavigate } from "react-router-dom"; import { Link, RouteObject, useNavigate } from "react-router-dom";
import BlueWallet from "Icons/BlueWallet"; import BlueWallet from "Icons/BlueWallet";
import ConnectLNC from "Pages/settings/wallet/LNC"; import ConnectLNC from "Pages/settings/wallet/LNC";
@ -15,21 +15,26 @@ const WalletSettings = () => {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<> <>
<Link to="/wallet">
<button type="button">
<FormattedMessage defaultMessage="View Wallets" />
</button>
</Link>
<h3> <h3>
<FormattedMessage defaultMessage="Connect Wallet" /> <FormattedMessage defaultMessage="Connect Wallet" />
</h3> </h3>
<div className="wallet-grid"> <div className="wallet-grid">
<div className="card" onClick={() => navigate("/settings/wallet/lnc")}> <div onClick={() => navigate("/settings/wallet/lnc")}>
<img src={LndLogo} width={100} /> <img src={LndLogo} width={100} />
<h3 className="f-end">LND with LNC</h3> <h3>LND with LNC</h3>
</div> </div>
<div className="card" onClick={() => navigate("/settings/wallet/lndhub")}> <div onClick={() => navigate("/settings/wallet/lndhub")}>
<BlueWallet width={100} height={100} /> <BlueWallet width={100} height={100} />
<h3 className="f-end">LNDHub</h3> <h3>LNDHub</h3>
</div> </div>
<div className="card" onClick={() => navigate("/settings/wallet/nwc")}> <div onClick={() => navigate("/settings/wallet/nwc")}>
<NostrIcon width={100} height={100} /> <NostrIcon width={100} height={100} />
<h3 className="f-end">Nostr Wallet Connect</h3> <h3>Nostr Wallet Connect</h3>
</div> </div>
</div> </div>
</> </>

View File

@ -42,7 +42,7 @@ export default function LNForwardAddress({ handle }: { handle: ManageHandle }) {
} }
return ( return (
<div className="card"> <div>
<h4> <h4>
<FormattedMessage defaultMessage="Update Lightning Address" /> <FormattedMessage defaultMessage="Update Lightning Address" />
</h4> </h4>

View File

@ -29,37 +29,33 @@ export default function ListHandles() {
defaultMessage="It looks like you dont have any, check {link} to buy one!" defaultMessage="It looks like you dont have any, check {link} to buy one!"
values={{ values={{
link: ( link: (
<Link to="/verification"> <Link to="/nostr-address">
<FormattedMessage defaultMessage="Verification" /> <FormattedMessage defaultMessage="Buy Handle" />
</Link> </Link>
), ),
}} }}
/> />
)} )}
{handles.map(a => ( {handles.map(a => (
<div className="card flex" key={a.id}> <div className="flex f-space" key={a.id}>
<div className="f-grow"> <h4 className="nip05">
<h4 className="nip05"> {a.handle}@
{a.handle}@ <span className="domain" data-domain={a.domain?.toLowerCase()}>
<span className="domain" data-domain={a.domain?.toLowerCase()}> {a.domain}
{a.domain} </span>
</span> </h4>
</h4> <button
</div> onClick={() =>
<div> navigate("manage", {
<button state: a,
onClick={() => })
navigate("manage", { }>
state: a, <FormattedMessage defaultMessage="Manage" />
}) </button>
}>
<FormattedMessage defaultMessage="Manage" />
</button>
</div>
</div> </div>
))} ))}
{handles.length > 0 && ( {handles.length > 0 && (
<button onClick={() => navigate("/verification")}> <button onClick={() => navigate("/nostr-address")}>
<FormattedMessage defaultMessage="Buy Handle" /> <FormattedMessage defaultMessage="Buy Handle" />
</button> </button>
)} )}

View File

@ -28,7 +28,7 @@ export default function TransferHandle({ handle }: { handle: ManageHandle }) {
} }
return ( return (
<div className="card"> <div>
<h4> <h4>
<FormattedMessage defaultMessage="Transfer to Pubkey" /> <FormattedMessage defaultMessage="Transfer to Pubkey" />
</h4> </h4>

View File

@ -31,7 +31,7 @@ const ConnectCashu = () => {
data: mintUrl, data: mintUrl,
} as WalletConfig; } as WalletConfig;
Wallets.add(newWallet); Wallets.add(newWallet);
navigate("/wallet"); navigate("/settings/wallet");
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
setError((e as Error).message); setError((e as Error).message);

View File

@ -46,7 +46,7 @@ const ConnectLNC = () => {
active: true, active: true,
info: unwrap(walletInfo), info: unwrap(walletInfo),
}); });
navigate("/wallet"); navigate("/settings/wallet");
} }
function flowConnect() { function flowConnect() {

View File

@ -29,7 +29,7 @@ const ConnectLNDHub = () => {
} as WalletConfig; } as WalletConfig;
Wallets.add(newWallet); Wallets.add(newWallet);
navigate("/wallet"); navigate("/settings/wallet");
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
setError((e as Error).message); setError((e as Error).message);

View File

@ -29,7 +29,7 @@ const ConnectNostrWallet = () => {
} as WalletConfig; } as WalletConfig;
Wallets.add(newWallet); Wallets.add(newWallet);
navigate("/wallet"); navigate("/settings/wallet");
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
setError((e as Error).message); setError((e as Error).message);

View File

@ -33,7 +33,7 @@ export default function ManageSubscriptionPage() {
return <PageSpinner />; return <PageSpinner />;
} }
return ( return (
<> <div className="main-content p flex-column g24">
<h2> <h2>
<FormattedMessage defaultMessage="Subscriptions" /> <FormattedMessage defaultMessage="Subscriptions" />
</h2> </h2>
@ -60,6 +60,6 @@ export default function ManageSubscriptionPage() {
</p> </p>
)} )}
{error && <b className="error">{mapSubscriptionErrorCode(error)}</b>} {error && <b className="error">{mapSubscriptionErrorCode(error)}</b>}
</> </div>
); );
} }

View File

@ -8,7 +8,7 @@ import Icon from "Icons/Icon";
import useEventPublisher from "Feed/EventPublisher"; import useEventPublisher from "Feed/EventPublisher";
import SendSats from "Element/SendSats"; import SendSats from "Element/SendSats";
import Nip5Service from "Element/Nip5Service"; import Nip5Service from "Element/Nip5Service";
import { SnortNostrAddressService } from "Pages/Verification"; import { SnortNostrAddressService } from "Pages/NostrAddressPage";
import Nip05 from "Element/Nip05"; import Nip05 from "Element/Nip05";
export default function SubscriptionCard({ sub }: { sub: Subscription }) { export default function SubscriptionCard({ sub }: { sub: Subscription }) {
@ -62,7 +62,7 @@ export default function SubscriptionCard({ sub }: { sub: Subscription }) {
return ( return (
<> <>
<div className="card"> <div className="p">
<div className="flex card-title"> <div className="flex card-title">
<Icon name="badge" className="mr5" size={25} /> <Icon name="badge" className="mr5" size={25} />
{mapPlanName(sub.type)} {mapPlanName(sub.type)}

View File

@ -1,4 +1,4 @@
.subscribe-page > div.card { .subscribe-page > div {
margin: 5px; margin: 5px;
min-height: 400px; min-height: 400px;
user-select: none; user-select: none;
@ -18,7 +18,7 @@
flex-direction: column; flex-direction: column;
} }
.subscribe-page > div.card { .subscribe-page > div {
flex: unset; flex: unset;
} }
} }

View File

@ -77,11 +77,11 @@ export function SubscribePage() {
return ( return (
<> <>
<div className="flex subscribe-page"> <div className="flex subscribe-page main-content">
{Plans.map(a => { {Plans.map(a => {
const lower = Plans.filter(b => b.id < a.id); const lower = Plans.filter(b => b.id < a.id);
return ( return (
<div className={`card flex f-col${a.disabled ? " disabled" : ""}`}> <div className={`p flex-column${a.disabled ? " disabled" : ""}`}>
<div className="f-grow"> <div className="f-grow">
<h2>{mapPlanName(a.id)}</h2> <h2>{mapPlanName(a.id)}</h2>
<p> <p>

View File

@ -27,14 +27,18 @@ export async function openFile(): Promise<File | undefined> {
return new Promise(resolve => { return new Promise(resolve => {
const elm = document.createElement("input"); const elm = document.createElement("input");
elm.type = "file"; elm.type = "file";
elm.onchange = (e: Event) => { const handleInput = (e: Event) => {
console.debug(e);
const elm = e.target as HTMLInputElement; const elm = e.target as HTMLInputElement;
if (elm.files) { if ((elm.files?.length ?? 0) > 0) {
resolve(elm.files[0]); resolve(elm.files![0]);
} else { } else {
resolve(undefined); resolve(undefined);
} }
}; };
elm.onchange = e => handleInput(e);
elm.onblur = e => handleInput(e);
elm.click(); elm.click();
}); });
} }

View File

@ -14,11 +14,11 @@ export class Nip5Task extends BaseUITask {
return ( return (
<p> <p>
<FormattedMessage <FormattedMessage
defaultMessage="Hey, it looks like you dont have a NIP-05 handle yet, you should get one! Check out {link}" defaultMessage="Hey, it looks like you dont have a Nostr Address yet, you should get one! Check out {link}"
values={{ values={{
link: ( link: (
<Link to="/verification"> <Link to="/nostr-address">
<FormattedMessage defaultMessage="NIP-05 Shop" /> <FormattedMessage defaultMessage="Buy nostr address" />
</Link> </Link>
), ),
}} }}

View File

@ -125,6 +125,10 @@ body #root > div:not(.page) header {
} }
} }
.p {
padding: 12px 16px;
}
.card { .card {
padding: 12px 16px; padding: 12px 16px;
border-bottom: 1px solid var(--gray-superdark); border-bottom: 1px solid var(--gray-superdark);
@ -158,7 +162,6 @@ button {
padding: 6px 12px; padding: 6px 12px;
font-weight: 600; font-weight: 600;
color: white; color: white;
min-height: 35px;
font-size: var(--font-size); font-size: var(--font-size);
background-color: var(--highlight); background-color: var(--highlight);
border: none; border: none;
@ -296,12 +299,13 @@ input[type="password"],
input[type="number"], input[type="number"],
select, select,
textarea { textarea {
padding: 12px; padding: 12px 16px;
color: var(--font-color); color: var(--font-color);
background: transparent; background: transparent;
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px; border-radius: 12px;
outline: none; outline: none;
line-height: 24px; /* 150% */
} }
.light input[type="text"], .light input[type="text"],
@ -338,6 +342,11 @@ input:disabled {
min-width: 0; min-width: 0;
} }
.flex-column {
display: flex;
flex-direction: column;
}
.f-center { .f-center {
justify-content: center; justify-content: center;
} }

View File

@ -19,7 +19,7 @@ import { RootRoutes } from "Pages/Root";
import NotificationsPage from "Pages/Notifications"; import NotificationsPage from "Pages/Notifications";
import SettingsPage, { SettingsRoutes } from "Pages/SettingsPage"; import SettingsPage, { SettingsRoutes } from "Pages/SettingsPage";
import ErrorPage from "Pages/ErrorPage"; import ErrorPage from "Pages/ErrorPage";
import VerificationPage from "Pages/Verification"; import NostrAddressPage from "Pages/NostrAddressPage";
import MessagesPage from "Pages/MessagesPage"; import MessagesPage from "Pages/MessagesPage";
import DonatePage from "Pages/DonatePage"; import DonatePage from "Pages/DonatePage";
import SearchPage from "Pages/SearchPage"; import SearchPage from "Pages/SearchPage";
@ -125,8 +125,8 @@ export const router = createBrowserRouter([
children: SettingsRoutes, children: SettingsRoutes,
}, },
{ {
path: "/verification", path: "/nostr-address",
element: <VerificationPage />, element: <NostrAddressPage />,
}, },
{ {
path: "/messages/:id?", path: "/messages/:id?",