Preferences page
This commit is contained in:
@ -11,19 +11,21 @@ interface AvatarProps {
|
||||
user?: UserMetadata;
|
||||
onClick?: () => void;
|
||||
size?: number;
|
||||
image?: string;
|
||||
}
|
||||
const Avatar = ({ user, size, onClick }: AvatarProps) => {
|
||||
const Avatar = ({ user, size, onClick, image }: AvatarProps) => {
|
||||
const [url, setUrl] = useState<string>(Nostrich);
|
||||
const { proxy } = useImgProxy();
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.picture) {
|
||||
const url = proxy(user.picture, size ?? 120);
|
||||
setUrl(url);
|
||||
const url = image ?? user?.picture;
|
||||
if (url) {
|
||||
const proxyUrl = proxy(url, size ?? 120);
|
||||
setUrl(proxyUrl);
|
||||
} else {
|
||||
setUrl(Nostrich);
|
||||
}
|
||||
}, [user]);
|
||||
}, [user, image]);
|
||||
|
||||
const backgroundImage = `url(${url})`;
|
||||
const style = { "--img-url": backgroundImage } as CSSProperties;
|
||||
|
20
packages/app/src/Element/AvatarEditor.css
Normal file
20
packages/app/src/Element/AvatarEditor.css
Normal 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;
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import "./AvatarEditor.css";
|
||||
import Icon from "Icons/Icon";
|
||||
import { useState } from "react";
|
||||
import useFileUpload from "Upload";
|
||||
import { openFile, unwrap } from "SnortUtils";
|
||||
import Spinner from "Icons/Spinner";
|
||||
|
||||
interface AvatarEditorProps {
|
||||
picture?: string;
|
||||
@ -11,9 +13,11 @@ interface AvatarEditorProps {
|
||||
export default function AvatarEditor({ picture, onPictureChange }: AvatarEditorProps) {
|
||||
const uploader = useFileUpload();
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function uploadFile() {
|
||||
setError("");
|
||||
setLoading(true);
|
||||
try {
|
||||
const f = await openFile();
|
||||
if (f) {
|
||||
@ -32,6 +36,7 @@ export default function AvatarEditor({ picture, onPictureChange }: AvatarEditorP
|
||||
setError(`Upload failed`);
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -39,7 +44,7 @@ export default function AvatarEditor({ picture, onPictureChange }: AvatarEditorP
|
||||
<div className="flex f-center">
|
||||
<div style={{ backgroundImage: `url(${picture})` }} className="avatar">
|
||||
<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>
|
||||
|
@ -41,6 +41,8 @@ export function updatePreferences(state: LoginSession, p: UserPreferences) {
|
||||
|
||||
export function logout(k: HexKey) {
|
||||
LoginStore.removeSession(k);
|
||||
//TODO: delete giftwarps for:k
|
||||
//TODO: delete notifications for:k
|
||||
}
|
||||
|
||||
export function markNotificationsRead(state: LoginSession) {
|
||||
|
@ -158,6 +158,7 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
const pk = unwrap(s.publicKey);
|
||||
if (this.#accounts.has(pk)) {
|
||||
this.#accounts.set(pk, s);
|
||||
console.debug("SET SESSION", s);
|
||||
this.#save();
|
||||
}
|
||||
}
|
||||
@ -175,7 +176,7 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
const s = this.#activeAccount ? this.#accounts.get(this.#activeAccount) : undefined;
|
||||
if (!s) return LoggedOut;
|
||||
|
||||
return s;
|
||||
return { ...s };
|
||||
}
|
||||
|
||||
#createPublisher(l: LoginSession) {
|
||||
|
@ -74,7 +74,7 @@ const DonatePage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="main-content m5">
|
||||
<div className="main-content p">
|
||||
<h2>
|
||||
<FormattedMessage defaultMessage="Help fund the development of Snort" />
|
||||
</h2>
|
||||
|
@ -5,8 +5,6 @@ import Nip5Service from "Element/Nip5Service";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
import "./Verification.css";
|
||||
|
||||
export const SnortNostrAddressService = {
|
||||
name: "Snort",
|
||||
service: `${ApiHost}/api/v1/n5sp`,
|
||||
@ -26,11 +24,11 @@ export const Nip5Services = [
|
||||
},
|
||||
];
|
||||
|
||||
export default function VerificationPage() {
|
||||
export default function NostrAddressPage() {
|
||||
return (
|
||||
<div className="main-content verification">
|
||||
<div className="main-content p">
|
||||
<h2>
|
||||
<FormattedMessage {...messages.GetVerified} />
|
||||
<FormattedMessage defaultMessage="Buy nostr address" />
|
||||
</h2>
|
||||
<p>
|
||||
<FormattedMessage {...messages.Nip05} />
|
@ -86,7 +86,7 @@ export default function NotificationsPage() {
|
||||
|
||||
const timeGrouped = useMemo(() => {
|
||||
return orderDescending([...notifications])
|
||||
.filter(a => !isMuted(a.pubkey))
|
||||
.filter(a => !isMuted(a.pubkey) && findTag(a, "p") === login.publicKey)
|
||||
.reduce((acc, v) => {
|
||||
const key = `${timeKey(v)}:${notificationContext(v as TaggedRawEvent)?.encode()}:${v.kind}`;
|
||||
if (acc.has(key)) {
|
||||
|
@ -333,11 +333,19 @@ export const RootRoutes = [
|
||||
},
|
||||
{
|
||||
path: "trending/people",
|
||||
element: <TrendingUsers />,
|
||||
element: (
|
||||
<div className="p">
|
||||
<TrendingUsers />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "suggested",
|
||||
element: <SuggestedProfiles />,
|
||||
element: (
|
||||
<div className="p">
|
||||
<SuggestedProfiles />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/t/:tag",
|
||||
|
@ -29,31 +29,33 @@ export const SettingsRoutes: RouteObject[] = [
|
||||
{
|
||||
path: "",
|
||||
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,
|
||||
];
|
||||
|
@ -1,3 +0,0 @@
|
||||
.verification a {
|
||||
color: var(--highlight);
|
||||
}
|
@ -204,15 +204,11 @@ export default function WalletPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="main-content">
|
||||
<div className="main-content p">
|
||||
{error && <b className="error">{error}</b>}
|
||||
{walletList()}
|
||||
{unlockWallet()}
|
||||
{walletInfo()}
|
||||
|
||||
<button onClick={() => Wallets.remove(unwrap(walletState.config).id)}>
|
||||
<FormattedMessage defaultMessage="Delete Account" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
.zap-pool input[type="range"] {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.zap-pool h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ export default function ZapPoolPage() {
|
||||
|
||||
const sumPending = zapPool.reduce((acc, v) => acc + v.sum, 0);
|
||||
return (
|
||||
<div className="zap-pool main-content">
|
||||
<div className="zap-pool main-content p">
|
||||
<h1>
|
||||
<FormattedMessage defaultMessage="Zap Pool" />
|
||||
</h1>
|
||||
@ -150,7 +150,7 @@ export default function ZapPoolPage() {
|
||||
</AsyncButton>
|
||||
)}
|
||||
</p>
|
||||
<div className="card">
|
||||
<div>
|
||||
<ZapTarget
|
||||
target={
|
||||
zapPool.find(b => b.pubkey === bech32ToHex(SnortPubKey) && b.type === ZapPoolRecipientType.Generic) ?? {
|
||||
@ -166,7 +166,7 @@ export default function ZapPoolPage() {
|
||||
<FormattedMessage defaultMessage="Relays" />
|
||||
</h3>
|
||||
{relayConnections.map(a => (
|
||||
<div className="card">
|
||||
<div>
|
||||
<h4>{getRelayName(a.address)}</h4>
|
||||
<ZapTarget
|
||||
target={
|
||||
@ -184,7 +184,7 @@ export default function ZapPoolPage() {
|
||||
<FormattedMessage defaultMessage="File hosts" />
|
||||
</h3>
|
||||
{UploaderServices.map(a => (
|
||||
<div className="card">
|
||||
<div>
|
||||
<h4>{a.name}</h4>
|
||||
<ZapTarget
|
||||
target={
|
||||
@ -202,7 +202,7 @@ export default function ZapPoolPage() {
|
||||
<FormattedMessage defaultMessage="Data Providers" />
|
||||
</h3>
|
||||
{DataProviders.map(a => (
|
||||
<div className="card">
|
||||
<div>
|
||||
<h4>{a.name}</h4>
|
||||
<ZapTarget
|
||||
target={
|
||||
|
@ -25,7 +25,7 @@ export default function DiscoverFollows() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="main-content new-user" dir="auto">
|
||||
<div className="main-content new-user p" dir="auto">
|
||||
<Logo />
|
||||
<div className="progress-bar">
|
||||
<div className="progress"></div>
|
||||
@ -45,6 +45,9 @@ export default function DiscoverFollows() {
|
||||
<FormattedMessage {...messages.PopularAccounts} />
|
||||
</h3>
|
||||
{sortedReccomends.length > 0 && <FollowListBase pubkeys={sortedReccomends} showAbout={true} />}
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Trending Users" />
|
||||
</h3>
|
||||
<TrendingUsers />
|
||||
</div>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import { useUserProfile } from "@snort/system-react";
|
||||
|
||||
import Logo from "Element/Logo";
|
||||
import { Nip5Services } from "Pages/Verification";
|
||||
import { Nip5Services } from "Pages/NostrAddressPage";
|
||||
import Nip5Service from "Element/Nip5Service";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
@ -90,7 +90,7 @@ export default function NewUserFlow() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="main-content new-user" dir="auto">
|
||||
<div className="main-content new-user p" dir="auto">
|
||||
<Logo />
|
||||
<div className="progress-bar">
|
||||
<div className="progress progress-first"></div>
|
||||
@ -98,31 +98,25 @@ export default function NewUserFlow() {
|
||||
<h1>
|
||||
<FormattedMessage {...messages.SaveKeys} />
|
||||
</h1>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<FormattedMessage defaultMessage="Language" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
value={login.preferences.language || DefaultPreferences.language}
|
||||
onChange={e =>
|
||||
updatePreferences(login, {
|
||||
...login.preferences,
|
||||
language: e.target.value,
|
||||
})
|
||||
}
|
||||
style={{ textTransform: "capitalize" }}>
|
||||
{AllLanguageCodes.sort().map(a => (
|
||||
<option value={a}>
|
||||
{new Intl.DisplayNames([a], {
|
||||
type: "language",
|
||||
}).of(a)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex f-space">
|
||||
<FormattedMessage defaultMessage="Language" />
|
||||
<select
|
||||
value={login.preferences.language || DefaultPreferences.language}
|
||||
onChange={e =>
|
||||
updatePreferences(login, {
|
||||
...login.preferences,
|
||||
language: e.target.value,
|
||||
})
|
||||
}
|
||||
style={{ textTransform: "capitalize" }}>
|
||||
{AllLanguageCodes.sort().map(a => (
|
||||
<option value={a}>
|
||||
{new Intl.DisplayNames([a], {
|
||||
type: "language",
|
||||
}).of(a)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<p>
|
||||
<FormattedMessage {...messages.SaveKeysHelp} />
|
||||
|
@ -47,7 +47,7 @@ export default function ProfileSetup() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="main-content new-user" dir="auto">
|
||||
<div className="main-content new-user p" dir="auto">
|
||||
<Logo />
|
||||
<div className="progress-bar">
|
||||
<div className="progress progress-second"></div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
.export-keys > .copy {
|
||||
.copy.dashed {
|
||||
padding: 12px 16px;
|
||||
border: 2px dashed #222222;
|
||||
border-radius: 16px;
|
||||
|
@ -10,18 +10,18 @@ import { hexToBech32 } from "SnortUtils";
|
||||
export default function ExportKeys() {
|
||||
const { publicKey, privateKey, generatedEntropy } = useLogin();
|
||||
return (
|
||||
<div className="export-keys">
|
||||
<div className="flex-column g12">
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Public Key" />
|
||||
</h3>
|
||||
<Copy text={hexToBech32("npub", publicKey ?? "")} maxSize={48} className="mb10" />
|
||||
<Copy text={encodeTLV(NostrPrefix.Profile, publicKey ?? "")} maxSize={48} />
|
||||
<Copy text={hexToBech32("npub", publicKey ?? "")} className="dashed" />
|
||||
<Copy text={encodeTLV(NostrPrefix.Profile, publicKey ?? "")} className="dashed" />
|
||||
{privateKey && (
|
||||
<>
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Private Key" />
|
||||
</h3>
|
||||
<Copy text={hexToBech32("nsec", privateKey)} maxSize={48} />
|
||||
<Copy text={hexToBech32("nsec", privateKey)} className="dashed" />
|
||||
</>
|
||||
)}
|
||||
{generatedEntropy && (
|
||||
@ -29,7 +29,7 @@ export default function ExportKeys() {
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Mnemonic" />
|
||||
</h3>
|
||||
<Copy text={hexToMnemonic(generatedEntropy ?? "")} maxSize={48} />
|
||||
<Copy text={hexToMnemonic(generatedEntropy ?? "")} className="dashed" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,8 +1,14 @@
|
||||
.preferences small {
|
||||
margin-top: 0.5em;
|
||||
color: var(--font-secondary-color);
|
||||
}
|
||||
|
||||
.preferences select {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.preferences h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px; /* 137.5% */
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ export const AllLanguageCodes = [
|
||||
const PreferencesPage = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const login = useLogin();
|
||||
console.debug(login);
|
||||
const perf = login.preferences;
|
||||
const [emoji, setEmoji] = useState<Array<{ name: string; char: string }>>([]);
|
||||
|
||||
@ -42,17 +43,15 @@ const PreferencesPage = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="preferences">
|
||||
<div className="preferences flex-column g24">
|
||||
<h3>
|
||||
<FormattedMessage {...messages.Preferences} />
|
||||
</h3>
|
||||
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<FormattedMessage defaultMessage="Language" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex f-space w-max">
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Language" />
|
||||
</h4>
|
||||
<div>
|
||||
<select
|
||||
value={perf.language || DefaultPreferences.language}
|
||||
@ -73,12 +72,10 @@ const PreferencesPage = () => {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<FormattedMessage {...messages.Theme} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex f-space w-max">
|
||||
<h4>
|
||||
<FormattedMessage {...messages.Theme} />
|
||||
</h4>
|
||||
<div>
|
||||
<select
|
||||
value={perf.theme}
|
||||
@ -100,12 +97,10 @@ const PreferencesPage = () => {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<FormattedMessage {...messages.DefaultRootTab} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex f-space w-max">
|
||||
<h4>
|
||||
<FormattedMessage {...messages.DefaultRootTab} />
|
||||
</h4>
|
||||
<div>
|
||||
<select
|
||||
value={perf.defaultRootTab}
|
||||
@ -127,16 +122,17 @@ const PreferencesPage = () => {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<div className="flex w-max">
|
||||
<div className="flex-column g8">
|
||||
<h4>
|
||||
<FormattedMessage {...messages.AutoloadMedia} />
|
||||
</div>
|
||||
</h4>
|
||||
<small>
|
||||
<FormattedMessage {...messages.AutoloadMediaHelp} />
|
||||
</small>
|
||||
<div className="mt10">
|
||||
<div className="w-max">
|
||||
<select
|
||||
className="w-max"
|
||||
value={perf.autoLoadMedia}
|
||||
onChange={e =>
|
||||
updatePreferences(login, {
|
||||
@ -157,11 +153,11 @@ const PreferencesPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<div className="flex f-space w-max">
|
||||
<div className="flex-column g8">
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Proof of Work" />
|
||||
</div>
|
||||
</h4>
|
||||
<small>
|
||||
<FormattedMessage defaultMessage="Amount of work to apply to all published events" />
|
||||
</small>
|
||||
@ -175,12 +171,10 @@ const PreferencesPage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<FormattedMessage defaultMessage="Default Zap amount" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex f-space w-max">
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Default Zap amount" />
|
||||
</h4>
|
||||
<div>
|
||||
<input
|
||||
type="number"
|
||||
@ -190,11 +184,11 @@ const PreferencesPage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<div className="flex f-space w-max">
|
||||
<div className="flex-column g8">
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Auto Zap" />
|
||||
</div>
|
||||
</h4>
|
||||
<small>
|
||||
<FormattedMessage defaultMessage="Automatically zap every note when loaded" />
|
||||
</small>
|
||||
@ -207,12 +201,12 @@ const PreferencesPage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex f-col">
|
||||
<div className="flex w-max">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<div className="flex-column">
|
||||
<div className="flex f-space">
|
||||
<div className="flex-column g8">
|
||||
<h4>
|
||||
<FormattedMessage {...messages.ImgProxy} />
|
||||
</div>
|
||||
</h4>
|
||||
<small>
|
||||
<FormattedMessage {...messages.ImgProxyHelp} />
|
||||
</small>
|
||||
@ -231,7 +225,7 @@ const PreferencesPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
{perf.imgProxyConfig && (
|
||||
<div className="w-max mt10 form">
|
||||
<div className="w-max form">
|
||||
<div className="form-group">
|
||||
<div>
|
||||
<FormattedMessage {...messages.ServiceUrl} />
|
||||
@ -307,11 +301,11 @@ const PreferencesPage = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<div className="flex f-space w-max">
|
||||
<div className="flex-column g8">
|
||||
<h4>
|
||||
<FormattedMessage {...messages.EnableReactions} />
|
||||
</div>
|
||||
</h4>
|
||||
<small>
|
||||
<FormattedMessage {...messages.EnableReactionsHelp} />
|
||||
</small>
|
||||
@ -324,43 +318,39 @@ const PreferencesPage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<FormattedMessage {...messages.ReactionEmoji} />
|
||||
</div>
|
||||
<small>
|
||||
<FormattedMessage {...messages.ReactionEmojiHelp} />
|
||||
</small>
|
||||
<div className="mt10">
|
||||
<select
|
||||
className="emoji-selector"
|
||||
value={perf.reactionEmoji}
|
||||
onChange={e =>
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
reactionEmoji: e.target.value,
|
||||
})
|
||||
}>
|
||||
<option value="+">
|
||||
+ <FormattedMessage {...messages.Default} />
|
||||
<div className="flex-column g8">
|
||||
<h4>
|
||||
<FormattedMessage {...messages.ReactionEmoji} />
|
||||
</h4>
|
||||
<small>
|
||||
<FormattedMessage {...messages.ReactionEmojiHelp} />
|
||||
</small>
|
||||
<select
|
||||
className="emoji-selector"
|
||||
value={perf.reactionEmoji}
|
||||
onChange={e =>
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
reactionEmoji: e.target.value,
|
||||
})
|
||||
}>
|
||||
<option value="+">
|
||||
+ <FormattedMessage {...messages.Default} />
|
||||
</option>
|
||||
{emoji.map(({ name, char }) => {
|
||||
return (
|
||||
<option value={char}>
|
||||
{name} {char}
|
||||
</option>
|
||||
{emoji.map(({ name, char }) => {
|
||||
return (
|
||||
<option value={char}>
|
||||
{name} {char}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<div className="flex f-space">
|
||||
<div className="flex-column g8">
|
||||
<h4>
|
||||
<FormattedMessage {...messages.ConfirmReposts} />
|
||||
</div>
|
||||
</h4>
|
||||
<small>
|
||||
<FormattedMessage {...messages.ConfirmRepostsHelp} />
|
||||
</small>
|
||||
@ -373,11 +363,11 @@ const PreferencesPage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<div className="flex f-space">
|
||||
<div className="flex-column g8">
|
||||
<h4>
|
||||
<FormattedMessage {...messages.ShowLatest} />
|
||||
</div>
|
||||
</h4>
|
||||
<small>
|
||||
<FormattedMessage {...messages.ShowLatestHelp} />
|
||||
</small>
|
||||
@ -390,37 +380,33 @@ const PreferencesPage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<FormattedMessage {...messages.FileUpload} />
|
||||
</div>
|
||||
<small>
|
||||
<FormattedMessage {...messages.FileUploadHelp} />
|
||||
</small>
|
||||
<div className="mt10">
|
||||
<select
|
||||
value={perf.fileUploader}
|
||||
onChange={e =>
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
fileUploader: e.target.value,
|
||||
} as UserPreferences)
|
||||
}>
|
||||
<option value="void.cat">
|
||||
void.cat <FormattedMessage {...messages.Default} />
|
||||
</option>
|
||||
<option value="nostr.build">nostr.build</option>
|
||||
<option value="nostrimg.com">nostrimg.com</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-column g8">
|
||||
<h4>
|
||||
<FormattedMessage {...messages.FileUpload} />
|
||||
</h4>
|
||||
<small>
|
||||
<FormattedMessage {...messages.FileUploadHelp} />
|
||||
</small>
|
||||
<select
|
||||
value={perf.fileUploader}
|
||||
onChange={e =>
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
fileUploader: e.target.value,
|
||||
} as UserPreferences)
|
||||
}>
|
||||
<option value="void.cat">
|
||||
void.cat <FormattedMessage {...messages.Default} />
|
||||
</option>
|
||||
<option value="nostr.build">nostr.build</option>
|
||||
<option value="nostrimg.com">nostrimg.com</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<div className="flex f-space">
|
||||
<div className="flex-column g8">
|
||||
<h4>
|
||||
<FormattedMessage {...messages.DebugMenus} />
|
||||
</div>
|
||||
</h4>
|
||||
<small>
|
||||
<FormattedMessage {...messages.DebugMenusHelp} />
|
||||
</small>
|
||||
|
@ -1,53 +1,51 @@
|
||||
.settings h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings .avatar {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-size: cover;
|
||||
border-radius: 100%;
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings .banner {
|
||||
width: 300px;
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.settings .image-settings {
|
||||
display: block;
|
||||
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;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--bg-color);
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
border-radius: 100%;
|
||||
height: 145px;
|
||||
margin-block-end: 45px;
|
||||
}
|
||||
|
||||
.avatar .edit.new {
|
||||
opacity: 0.5;
|
||||
.settings .image-settings > div {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.avatar .edit:hover {
|
||||
opacity: 0.5;
|
||||
.settings .image-settings .avatar-stack {
|
||||
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 {
|
||||
@ -59,3 +57,8 @@
|
||||
.settings .actions {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.settings small {
|
||||
font-size: 14px;
|
||||
color: var(--font-secondary-color);
|
||||
}
|
||||
|
@ -13,10 +13,8 @@ import useFileUpload from "Upload";
|
||||
import AsyncButton from "Element/AsyncButton";
|
||||
import { UserCache } from "Cache";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import AvatarEditor from "Element/AvatarEditor";
|
||||
import Icon from "Icons/Icon";
|
||||
|
||||
import messages from "./messages";
|
||||
import Avatar from "Element/Avatar";
|
||||
|
||||
export interface ProfileSettingsProps {
|
||||
avatar?: boolean;
|
||||
@ -31,7 +29,6 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
const uploader = useFileUpload();
|
||||
|
||||
const [name, setName] = useState<string>();
|
||||
const [displayName, setDisplayName] = useState<string>();
|
||||
const [picture, setPicture] = useState<string>();
|
||||
const [banner, setBanner] = useState<string>();
|
||||
const [about, setAbout] = useState<string>();
|
||||
@ -39,12 +36,9 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
const [nip05, setNip05] = useState<string>();
|
||||
const [lud16, setLud16] = useState<string>();
|
||||
|
||||
const avatarPicture = (picture?.length ?? 0) === 0 ? Nostrich : picture;
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
setName(user.name);
|
||||
setDisplayName(user.display_name);
|
||||
setPicture(user.picture);
|
||||
setBanner(user.banner);
|
||||
setAbout(user.about);
|
||||
@ -59,7 +53,6 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
const userCopy = {
|
||||
...user,
|
||||
name,
|
||||
display_name: displayName,
|
||||
about,
|
||||
picture,
|
||||
banner,
|
||||
@ -107,69 +100,62 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
}
|
||||
}
|
||||
|
||||
async function setNewAvatar() {
|
||||
const rsp = await uploadFile();
|
||||
if (rsp) {
|
||||
setPicture(rsp);
|
||||
}
|
||||
}
|
||||
|
||||
function editor() {
|
||||
return (
|
||||
<div className="editor form">
|
||||
<div className="form-group card">
|
||||
<div>
|
||||
<FormattedMessage {...messages.Name} />:
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" value={name} onChange={e => setName(e.target.value)} />
|
||||
<div className="flex f-col g24">
|
||||
<div className="flex f-col w-max g8">
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Name" />
|
||||
</h4>
|
||||
<input className="w-max" 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 className="form-group card">
|
||||
<div>
|
||||
<FormattedMessage {...messages.DisplayName} />:
|
||||
</div>
|
||||
<div>
|
||||
<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" />
|
||||
<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 className="flex f-col w-max g8">
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Lightning Address" />
|
||||
</h4>
|
||||
<input className="w-max" type="text" value={lud16} onChange={e => setLud16(e.target.value)} />
|
||||
</div>
|
||||
<AsyncButton onClick={() => saveProfile()}>
|
||||
<FormattedMessage defaultMessage="Save" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -179,28 +165,23 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex f-center image-settings">
|
||||
{(props.avatar ?? true) && (
|
||||
<div className="image-setting card">
|
||||
<div>
|
||||
<FormattedMessage {...messages.Avatar} />:
|
||||
</div>
|
||||
<AvatarEditor picture={avatarPicture} onPictureChange={p => setPicture(p)} />
|
||||
{(props.banner ?? true) && (
|
||||
<div
|
||||
style={{
|
||||
backgroundImage: `url(${(banner?.length ?? 0) === 0 ? Nostrich : banner})`,
|
||||
}}
|
||||
className="banner">
|
||||
<AsyncButton type="button" onClick={() => setNewBanner()}>
|
||||
<FormattedMessage defaultMessage="Upload" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
)}
|
||||
{(props.banner ?? true) && (
|
||||
<div className="image-setting card">
|
||||
<div>
|
||||
<FormattedMessage {...messages.Banner} />:
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundImage: `url(${(banner?.length ?? 0) === 0 ? Nostrich : banner})`,
|
||||
}}
|
||||
className="banner">
|
||||
<div className="edit" onClick={() => setNewBanner()}>
|
||||
<FormattedMessage {...messages.Edit} />
|
||||
</div>
|
||||
</div>
|
||||
{(props.avatar ?? true) && (
|
||||
<div className="avatar-stack">
|
||||
<Avatar user={user} image={picture} />
|
||||
<AsyncButton type="button" className="btn-rnd" onClick={() => setNewAvatar()}>
|
||||
<Icon name="upload-01" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -209,12 +190,5 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="settings">
|
||||
<h3>
|
||||
<FormattedMessage {...messages.EditProfile} />
|
||||
</h3>
|
||||
{settings()}
|
||||
</div>
|
||||
);
|
||||
return <div className="settings">{settings()}</div>;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ const RelayInfo = () => {
|
||||
<h3 className="pointer" onClick={() => navigate("/settings/relays")}>
|
||||
<FormattedMessage {...messages.Relays} />
|
||||
</h3>
|
||||
<div className="card">
|
||||
<div>
|
||||
<h3>{stats?.info?.name}</h3>
|
||||
<p>{stats?.info?.description}</p>
|
||||
|
||||
|
@ -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 {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings-row {
|
||||
display: grid;
|
||||
grid-template-columns: 22px 1fr 8px;
|
||||
grid-template-columns: 24px 1fr 24px;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
padding: 0.8em 1em;
|
||||
background: var(--note-bg);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
gap: 10px;
|
||||
margin-bottom: 5px;
|
||||
padding: 12px 16px;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.settings-row.inner {
|
||||
@ -24,12 +34,12 @@
|
||||
}
|
||||
|
||||
.settings-group-header {
|
||||
font-weight: 600;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 12px 16px;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
padding: 0.8em 1em;
|
||||
background-color: var(--note-bg);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.settings-row:hover,
|
||||
@ -41,11 +51,6 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.settings-row svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.settings-group-header .collapse-icon > svg {
|
||||
width: 8px;
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
import "./Root.css";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Outlet, useNavigate } from "react-router-dom";
|
||||
import Icon from "Icons/Icon";
|
||||
import { LoginStore, logout } from "Login";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { unwrap } from "SnortUtils";
|
||||
import { getCurrentSubscription } from "Subscription";
|
||||
import { CollapsedSection } from "Element/Collapsed";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
@ -21,80 +20,72 @@ const SettingsIndex = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="settings-nav">
|
||||
<CollapsedSection
|
||||
title={
|
||||
<div className="flex">
|
||||
<Icon name="user" className="mr10" />
|
||||
<FormattedMessage defaultMessage="Account" />
|
||||
</div>
|
||||
}
|
||||
className="settings-group-header">
|
||||
<div className="card">
|
||||
<div className="settings-row inner" onClick={() => navigate("profile")}>
|
||||
<Icon name="profile" />
|
||||
<FormattedMessage {...messages.Profile} />
|
||||
<Icon name="arrowFront" />
|
||||
</div>
|
||||
<div className="settings-row inner" onClick={() => navigate("relays")}>
|
||||
<Icon name="relay" />
|
||||
<FormattedMessage {...messages.Relays} />
|
||||
<Icon name="arrowFront" />
|
||||
</div>
|
||||
<div className="settings-row inner" onClick={() => navigate("keys")}>
|
||||
<Icon name="key" />
|
||||
<FormattedMessage defaultMessage="Export Keys" />
|
||||
<Icon name="arrowFront" />
|
||||
</div>
|
||||
<div className="settings-row inner" onClick={() => navigate("handle")}>
|
||||
<Icon name="badge" />
|
||||
<FormattedMessage defaultMessage="Nostr Adddress" />
|
||||
<Icon name="arrowFront" />
|
||||
</div>
|
||||
<div className="settings-row inner" onClick={() => navigate("/subscribe/manage")}>
|
||||
<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 className="settings-nav">
|
||||
<div>
|
||||
<div className="settings-row" onClick={() => navigate("profile")}>
|
||||
<Icon name="profile" size={24} />
|
||||
<FormattedMessage {...messages.Profile} />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
<div className="settings-row" onClick={() => navigate("relays")}>
|
||||
<Icon name="relay" size={24} />
|
||||
<FormattedMessage {...messages.Relays} />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
<div className="settings-row" onClick={() => navigate("keys")}>
|
||||
<Icon name="key" size={24} />
|
||||
<FormattedMessage defaultMessage="Export Keys" />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
<div className="settings-row" onClick={() => navigate("handle")}>
|
||||
<Icon name="badge" size={24} />
|
||||
<FormattedMessage defaultMessage="Nostr Adddress" />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
<div className="settings-row" onClick={() => navigate("/subscribe/manage")}>
|
||||
<Icon name="diamond" size={24} />
|
||||
<FormattedMessage defaultMessage="Subscription" />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
{sub && (
|
||||
<div className="settings-row" onClick={() => navigate("accounts")}>
|
||||
<Icon name="code-circle" size={24} />
|
||||
<FormattedMessage defaultMessage="Account Switcher" />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
</CollapsedSection>
|
||||
)}
|
||||
|
||||
<div className="settings-row" onClick={() => navigate("preferences")}>
|
||||
<Icon name="gear" />
|
||||
<Icon name="gear" size={24} />
|
||||
<FormattedMessage {...messages.Preferences} />
|
||||
<Icon name="arrowFront" />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
|
||||
<div className="settings-row" onClick={() => navigate("wallet")}>
|
||||
<Icon name="wallet" />
|
||||
<Icon name="wallet" size={24} />
|
||||
<FormattedMessage defaultMessage="Wallet" />
|
||||
<Icon name="arrowFront" />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
<div className="settings-row" onClick={() => navigate("/donate")}>
|
||||
<Icon name="heart" />
|
||||
<Icon name="heart" size={24} />
|
||||
<FormattedMessage {...messages.Donate} />
|
||||
<Icon name="arrowFront" />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
<div className="settings-row" onClick={() => navigate("/zap-pool")}>
|
||||
<Icon name="piggy-bank" />
|
||||
<Icon name="piggy-bank" size={24} />
|
||||
<FormattedMessage defaultMessage="Zap Pool" />
|
||||
<Icon name="arrowFront" />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
<div className="settings-row" onClick={handleLogout}>
|
||||
<Icon name="logout" />
|
||||
<Icon name="logout" size={24} />
|
||||
<FormattedMessage {...messages.LogOut} />
|
||||
<Icon name="arrowFront" />
|
||||
<Icon name="arrowFront" size={16} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<div>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -5,10 +5,10 @@
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.wallet-grid .card {
|
||||
.wallet-grid > div {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import "./WalletSettings.css";
|
||||
import LndLogo from "lnd-logo.png";
|
||||
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 ConnectLNC from "Pages/settings/wallet/LNC";
|
||||
@ -15,21 +15,26 @@ const WalletSettings = () => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<>
|
||||
<Link to="/wallet">
|
||||
<button type="button">
|
||||
<FormattedMessage defaultMessage="View Wallets" />
|
||||
</button>
|
||||
</Link>
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Connect Wallet" />
|
||||
</h3>
|
||||
<div className="wallet-grid">
|
||||
<div className="card" onClick={() => navigate("/settings/wallet/lnc")}>
|
||||
<div onClick={() => navigate("/settings/wallet/lnc")}>
|
||||
<img src={LndLogo} width={100} />
|
||||
<h3 className="f-end">LND with LNC</h3>
|
||||
<h3>LND with LNC</h3>
|
||||
</div>
|
||||
<div className="card" onClick={() => navigate("/settings/wallet/lndhub")}>
|
||||
<div onClick={() => navigate("/settings/wallet/lndhub")}>
|
||||
<BlueWallet width={100} height={100} />
|
||||
<h3 className="f-end">LNDHub</h3>
|
||||
<h3>LNDHub</h3>
|
||||
</div>
|
||||
<div className="card" onClick={() => navigate("/settings/wallet/nwc")}>
|
||||
<div onClick={() => navigate("/settings/wallet/nwc")}>
|
||||
<NostrIcon width={100} height={100} />
|
||||
<h3 className="f-end">Nostr Wallet Connect</h3>
|
||||
<h3>Nostr Wallet Connect</h3>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -42,7 +42,7 @@ export default function LNForwardAddress({ handle }: { handle: ManageHandle }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div>
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Update Lightning Address" />
|
||||
</h4>
|
||||
|
@ -29,37 +29,33 @@ export default function ListHandles() {
|
||||
defaultMessage="It looks like you dont have any, check {link} to buy one!"
|
||||
values={{
|
||||
link: (
|
||||
<Link to="/verification">
|
||||
<FormattedMessage defaultMessage="Verification" />
|
||||
<Link to="/nostr-address">
|
||||
<FormattedMessage defaultMessage="Buy Handle" />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{handles.map(a => (
|
||||
<div className="card flex" key={a.id}>
|
||||
<div className="f-grow">
|
||||
<h4 className="nip05">
|
||||
{a.handle}@
|
||||
<span className="domain" data-domain={a.domain?.toLowerCase()}>
|
||||
{a.domain}
|
||||
</span>
|
||||
</h4>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
onClick={() =>
|
||||
navigate("manage", {
|
||||
state: a,
|
||||
})
|
||||
}>
|
||||
<FormattedMessage defaultMessage="Manage" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex f-space" key={a.id}>
|
||||
<h4 className="nip05">
|
||||
{a.handle}@
|
||||
<span className="domain" data-domain={a.domain?.toLowerCase()}>
|
||||
{a.domain}
|
||||
</span>
|
||||
</h4>
|
||||
<button
|
||||
onClick={() =>
|
||||
navigate("manage", {
|
||||
state: a,
|
||||
})
|
||||
}>
|
||||
<FormattedMessage defaultMessage="Manage" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{handles.length > 0 && (
|
||||
<button onClick={() => navigate("/verification")}>
|
||||
<button onClick={() => navigate("/nostr-address")}>
|
||||
<FormattedMessage defaultMessage="Buy Handle" />
|
||||
</button>
|
||||
)}
|
||||
|
@ -28,7 +28,7 @@ export default function TransferHandle({ handle }: { handle: ManageHandle }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div>
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Transfer to Pubkey" />
|
||||
</h4>
|
||||
|
@ -31,7 +31,7 @@ const ConnectCashu = () => {
|
||||
data: mintUrl,
|
||||
} as WalletConfig;
|
||||
Wallets.add(newWallet);
|
||||
navigate("/wallet");
|
||||
navigate("/settings/wallet");
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError((e as Error).message);
|
||||
|
@ -46,7 +46,7 @@ const ConnectLNC = () => {
|
||||
active: true,
|
||||
info: unwrap(walletInfo),
|
||||
});
|
||||
navigate("/wallet");
|
||||
navigate("/settings/wallet");
|
||||
}
|
||||
|
||||
function flowConnect() {
|
||||
|
@ -29,7 +29,7 @@ const ConnectLNDHub = () => {
|
||||
} as WalletConfig;
|
||||
Wallets.add(newWallet);
|
||||
|
||||
navigate("/wallet");
|
||||
navigate("/settings/wallet");
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError((e as Error).message);
|
||||
|
@ -29,7 +29,7 @@ const ConnectNostrWallet = () => {
|
||||
} as WalletConfig;
|
||||
Wallets.add(newWallet);
|
||||
|
||||
navigate("/wallet");
|
||||
navigate("/settings/wallet");
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError((e as Error).message);
|
||||
|
@ -33,7 +33,7 @@ export default function ManageSubscriptionPage() {
|
||||
return <PageSpinner />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="main-content p flex-column g24">
|
||||
<h2>
|
||||
<FormattedMessage defaultMessage="Subscriptions" />
|
||||
</h2>
|
||||
@ -60,6 +60,6 @@ export default function ManageSubscriptionPage() {
|
||||
</p>
|
||||
)}
|
||||
{error && <b className="error">{mapSubscriptionErrorCode(error)}</b>}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import Icon from "Icons/Icon";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import SendSats from "Element/SendSats";
|
||||
import Nip5Service from "Element/Nip5Service";
|
||||
import { SnortNostrAddressService } from "Pages/Verification";
|
||||
import { SnortNostrAddressService } from "Pages/NostrAddressPage";
|
||||
import Nip05 from "Element/Nip05";
|
||||
|
||||
export default function SubscriptionCard({ sub }: { sub: Subscription }) {
|
||||
@ -62,7 +62,7 @@ export default function SubscriptionCard({ sub }: { sub: Subscription }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="card">
|
||||
<div className="p">
|
||||
<div className="flex card-title">
|
||||
<Icon name="badge" className="mr5" size={25} />
|
||||
{mapPlanName(sub.type)}
|
||||
|
@ -1,4 +1,4 @@
|
||||
.subscribe-page > div.card {
|
||||
.subscribe-page > div {
|
||||
margin: 5px;
|
||||
min-height: 400px;
|
||||
user-select: none;
|
||||
@ -18,7 +18,7 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.subscribe-page > div.card {
|
||||
.subscribe-page > div {
|
||||
flex: unset;
|
||||
}
|
||||
}
|
||||
|
@ -77,11 +77,11 @@ export function SubscribePage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex subscribe-page">
|
||||
<div className="flex subscribe-page main-content">
|
||||
{Plans.map(a => {
|
||||
const lower = Plans.filter(b => b.id < a.id);
|
||||
return (
|
||||
<div className={`card flex f-col${a.disabled ? " disabled" : ""}`}>
|
||||
<div className={`p flex-column${a.disabled ? " disabled" : ""}`}>
|
||||
<div className="f-grow">
|
||||
<h2>{mapPlanName(a.id)}</h2>
|
||||
<p>
|
||||
|
@ -27,14 +27,18 @@ export async function openFile(): Promise<File | undefined> {
|
||||
return new Promise(resolve => {
|
||||
const elm = document.createElement("input");
|
||||
elm.type = "file";
|
||||
elm.onchange = (e: Event) => {
|
||||
const handleInput = (e: Event) => {
|
||||
console.debug(e);
|
||||
const elm = e.target as HTMLInputElement;
|
||||
if (elm.files) {
|
||||
resolve(elm.files[0]);
|
||||
if ((elm.files?.length ?? 0) > 0) {
|
||||
resolve(elm.files![0]);
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
elm.onchange = e => handleInput(e);
|
||||
elm.onblur = e => handleInput(e);
|
||||
elm.click();
|
||||
});
|
||||
}
|
||||
|
@ -14,11 +14,11 @@ export class Nip5Task extends BaseUITask {
|
||||
return (
|
||||
<p>
|
||||
<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={{
|
||||
link: (
|
||||
<Link to="/verification">
|
||||
<FormattedMessage defaultMessage="NIP-05 Shop" />
|
||||
<Link to="/nostr-address">
|
||||
<FormattedMessage defaultMessage="Buy nostr address" />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
|
@ -125,6 +125,10 @@ body #root > div:not(.page) header {
|
||||
}
|
||||
}
|
||||
|
||||
.p {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--gray-superdark);
|
||||
@ -158,7 +162,6 @@ button {
|
||||
padding: 6px 12px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
min-height: 35px;
|
||||
font-size: var(--font-size);
|
||||
background-color: var(--highlight);
|
||||
border: none;
|
||||
@ -296,12 +299,13 @@ input[type="password"],
|
||||
input[type="number"],
|
||||
select,
|
||||
textarea {
|
||||
padding: 12px;
|
||||
padding: 12px 16px;
|
||||
color: var(--font-color);
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
outline: none;
|
||||
line-height: 24px; /* 150% */
|
||||
}
|
||||
|
||||
.light input[type="text"],
|
||||
@ -338,6 +342,11 @@ input:disabled {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.f-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import { RootRoutes } from "Pages/Root";
|
||||
import NotificationsPage from "Pages/Notifications";
|
||||
import SettingsPage, { SettingsRoutes } from "Pages/SettingsPage";
|
||||
import ErrorPage from "Pages/ErrorPage";
|
||||
import VerificationPage from "Pages/Verification";
|
||||
import NostrAddressPage from "Pages/NostrAddressPage";
|
||||
import MessagesPage from "Pages/MessagesPage";
|
||||
import DonatePage from "Pages/DonatePage";
|
||||
import SearchPage from "Pages/SearchPage";
|
||||
@ -125,8 +125,8 @@ export const router = createBrowserRouter([
|
||||
children: SettingsRoutes,
|
||||
},
|
||||
{
|
||||
path: "/verification",
|
||||
element: <VerificationPage />,
|
||||
path: "/nostr-address",
|
||||
element: <NostrAddressPage />,
|
||||
},
|
||||
{
|
||||
path: "/messages/:id?",
|
||||
|
Reference in New Issue
Block a user