mirror of
https://github.com/irislib/iris-messenger.git
synced 2024-09-16 16:23:28 +00:00
more fn comps
This commit is contained in:
parent
209f7a3fad
commit
3842ba5e9d
@ -18,6 +18,7 @@ module.exports = {
|
|||||||
plugins: ['simple-import-sort', '@typescript-eslint'],
|
plugins: ['simple-import-sort', '@typescript-eslint'],
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
'simple-import-sort/imports': [
|
'simple-import-sort/imports': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import Component from '../../BaseComponent';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import Key from '../../nostr/Key';
|
import Key from '../../nostr/Key';
|
||||||
import SocialNetwork from '../../nostr/SocialNetwork';
|
import SocialNetwork from '../../nostr/SocialNetwork';
|
||||||
import { translate as t } from '../../translations/Translation.mjs';
|
import { translate as t } from '../../translations/Translation.mjs';
|
||||||
@ -11,77 +12,61 @@ type Props = {
|
|||||||
onClick?: (e) => void;
|
onClick?: (e) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Block extends Component<Props> {
|
const Block = ({ id, showName, className, onClick }: Props) => {
|
||||||
key: string;
|
const cls = 'block-btn';
|
||||||
cls?: string;
|
const key = 'blocked';
|
||||||
actionDone: string;
|
const activeClass = 'blocked';
|
||||||
action: string;
|
const action = t('block');
|
||||||
activeClass: string;
|
const actionDone = t('blocked');
|
||||||
hoverAction: string;
|
const hoverAction = t('unblock');
|
||||||
|
|
||||||
constructor() {
|
const [hover, setHover] = useState(false);
|
||||||
super();
|
const [isBlocked, setIsBlocked] = useState(false);
|
||||||
this.cls = 'block-btn';
|
|
||||||
this.key = 'blocked';
|
|
||||||
this.activeClass = 'blocked';
|
|
||||||
this.action = t('block');
|
|
||||||
this.actionDone = t('blocked');
|
|
||||||
this.hoverAction = t('unblock');
|
|
||||||
this.state = { ...this.state, hover: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEnter = () => {
|
useEffect(() => {
|
||||||
this.setState({ hover: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseLeave = () => {
|
|
||||||
this.setState({ hover: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onClick(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const newValue = !this.state[this.key];
|
|
||||||
const hex = Key.toNostrHexAddress(this.props.id);
|
|
||||||
hex && SocialNetwork.block(hex, newValue);
|
|
||||||
this.props.onClick?.(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
SocialNetwork.getBlockedUsers((blocks) => {
|
SocialNetwork.getBlockedUsers((blocks) => {
|
||||||
const blocked = blocks?.has(Key.toNostrHexAddress(this.props.id) as string);
|
const blocked = blocks?.has(Key.toNostrHexAddress(id) as string);
|
||||||
this.setState({ blocked });
|
setIsBlocked(!!blocked);
|
||||||
});
|
});
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
setHover(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setHover(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onButtonClick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const newValue = !isBlocked;
|
||||||
|
const hex = Key.toNostrHexAddress(id);
|
||||||
|
hex && SocialNetwork.block(hex, newValue);
|
||||||
|
onClick?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
let buttonText;
|
||||||
|
if (isBlocked && hover) {
|
||||||
|
buttonText = hoverAction;
|
||||||
|
} else if (isBlocked && !hover) {
|
||||||
|
buttonText = actionDone;
|
||||||
|
} else {
|
||||||
|
buttonText = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const isBlocked = this.state[this.key];
|
<button
|
||||||
const isHovering = this.state.hover;
|
className={`${cls || key} ${isBlocked ? activeClass : ''} ${className || ''}`}
|
||||||
|
onClick={onButtonClick}
|
||||||
let buttonText;
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
if (isBlocked && isHovering) {
|
>
|
||||||
buttonText = this.hoverAction;
|
<span>
|
||||||
} else if (isBlocked && !isHovering) {
|
{t(buttonText)} {showName ? <Name pub={id} hideBadge={true} /> : ''}
|
||||||
buttonText = this.actionDone;
|
</span>
|
||||||
} else {
|
</button>
|
||||||
buttonText = this.action;
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={`${this.cls || this.key} ${isBlocked ? this.activeClass : ''} ${
|
|
||||||
this.props.className || ''
|
|
||||||
}`}
|
|
||||||
onClick={(e) => this.onClick(e)}
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{t(buttonText)} {this.props.showName ? <Name pub={this.props.id} hideBadge={true} /> : ''}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Block;
|
export default Block;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import Component from '../../BaseComponent';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import Key from '../../nostr/Key';
|
import Key from '../../nostr/Key';
|
||||||
import SocialNetwork from '../../nostr/SocialNetwork';
|
import SocialNetwork from '../../nostr/SocialNetwork';
|
||||||
import { translate as t } from '../../translations/Translation.mjs';
|
import { translate as t } from '../../translations/Translation.mjs';
|
||||||
@ -8,82 +9,69 @@ type Props = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Follow extends Component<Props> {
|
const Follow = ({ id, className }: Props) => {
|
||||||
key: string;
|
const key = 'follow';
|
||||||
cls?: string;
|
const activeClass = 'following';
|
||||||
actionDone: string;
|
const action = t('follow_btn');
|
||||||
action: string;
|
const actionDone = t('following_btn');
|
||||||
activeClass: string;
|
const hoverAction = t('unfollow_btn');
|
||||||
hoverAction: string;
|
|
||||||
|
|
||||||
constructor() {
|
const [hover, setHover] = useState(false);
|
||||||
super();
|
const [isFollowed, setIsFollowed] = useState(false);
|
||||||
this.key = 'follow';
|
|
||||||
this.activeClass = 'following';
|
|
||||||
this.action = t('follow_btn');
|
|
||||||
this.actionDone = t('following_btn');
|
|
||||||
this.hoverAction = t('unfollow_btn');
|
|
||||||
this.state = { ...this.state, hover: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEnter = () => {
|
useEffect(() => {
|
||||||
this.setState({ hover: true });
|
if (key === 'follow') {
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseLeave = () => {
|
|
||||||
this.setState({ hover: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onClick(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const newValue = !this.state[this.key];
|
|
||||||
const hex = Key.toNostrHexAddress(this.props.id);
|
|
||||||
if (!hex) return;
|
|
||||||
if (this.key === 'follow') {
|
|
||||||
SocialNetwork.setFollowed(hex, newValue);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.key === 'block') {
|
|
||||||
SocialNetwork.setBlocked(hex, newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
if (this.key === 'follow') {
|
|
||||||
SocialNetwork.getFollowedByUser(Key.getPubKey(), (follows) => {
|
SocialNetwork.getFollowedByUser(Key.getPubKey(), (follows) => {
|
||||||
const hex = Key.toNostrHexAddress(this.props.id);
|
const hex = Key.toNostrHexAddress(id);
|
||||||
const follow = hex && follows?.has(hex);
|
const follow = hex && follows?.has(hex);
|
||||||
this.setState({ follow });
|
setIsFollowed(!!follow);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
setHover(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setHover(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const newValue = !isFollowed;
|
||||||
|
const hex = Key.toNostrHexAddress(id);
|
||||||
|
if (!hex) return;
|
||||||
|
if (key === 'follow') {
|
||||||
|
SocialNetwork.setFollowed(hex, newValue);
|
||||||
|
setIsFollowed(newValue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
if (key === 'block') {
|
||||||
|
SocialNetwork.setBlocked(hex, newValue);
|
||||||
render() {
|
setIsFollowed(newValue);
|
||||||
const isFollowed = this.state[this.key];
|
|
||||||
const isHovering = this.state.hover;
|
|
||||||
|
|
||||||
let buttonText;
|
|
||||||
|
|
||||||
if (isFollowed && isHovering) {
|
|
||||||
buttonText = this.hoverAction;
|
|
||||||
} else if (isFollowed && !isHovering) {
|
|
||||||
buttonText = this.actionDone;
|
|
||||||
} else {
|
|
||||||
buttonText = this.action;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
let buttonText;
|
||||||
<button
|
if (isFollowed && hover) {
|
||||||
className={`btn ${this.props.className || this.key} ${isFollowed ? this.activeClass : ''}`}
|
buttonText = hoverAction;
|
||||||
onClick={(e) => this.onClick(e)}
|
} else if (isFollowed && !hover) {
|
||||||
onMouseEnter={this.handleMouseEnter} // handle hover state
|
buttonText = actionDone;
|
||||||
onMouseLeave={this.handleMouseLeave} // handle hover state
|
} else {
|
||||||
>
|
buttonText = action;
|
||||||
{t(buttonText)}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`btn ${className || key} ${isFollowed ? activeClass : ''}`}
|
||||||
|
onClick={onClick}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
{t(buttonText)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Follow;
|
export default Follow;
|
||||||
|
@ -1,36 +1,74 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import Key from '../../nostr/Key';
|
import Key from '../../nostr/Key';
|
||||||
import SocialNetwork from '../../nostr/SocialNetwork';
|
import SocialNetwork from '../../nostr/SocialNetwork';
|
||||||
import { translate as t } from '../../translations/Translation.mjs';
|
import { translate as t } from '../../translations/Translation.mjs';
|
||||||
|
import Name from '../user/Name';
|
||||||
|
|
||||||
import Block from './Block';
|
type Props = {
|
||||||
|
id: string;
|
||||||
|
showName?: boolean;
|
||||||
|
className?: string;
|
||||||
|
onClick?: (e) => void;
|
||||||
|
};
|
||||||
|
|
||||||
class Report extends Block {
|
const Report = ({ id, showName = false, className, onClick }: Props) => {
|
||||||
constructor() {
|
const cls = 'block'; // changed this from 'block-btn' to 'block'
|
||||||
super();
|
const key = 'reported'; // key updated for reporting
|
||||||
this.cls = 'block';
|
const activeClass = 'blocked'; // activeClass remains the same
|
||||||
this.key = 'reported';
|
const action = t('report_public'); // changed to report_public
|
||||||
this.activeClass = 'blocked';
|
const actionDone = t('reported'); // changed to reported
|
||||||
this.action = t('report_public');
|
const hoverAction = t('unreport'); // changed to unreport
|
||||||
this.actionDone = t('reported');
|
|
||||||
this.hoverAction = t('unreport');
|
|
||||||
}
|
|
||||||
|
|
||||||
onClick(e) {
|
const [hover, setHover] = useState(false);
|
||||||
e.preventDefault();
|
const [isReported, setIsReported] = useState(false);
|
||||||
const newValue = !this.state[this.key];
|
|
||||||
if (confirm(newValue ? 'Publicly report this user?' : 'Unreport user?')) {
|
|
||||||
const hex = Key.toNostrHexAddress(this.props.id);
|
|
||||||
hex && SocialNetwork.flag(hex, newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
useEffect(() => {
|
||||||
SocialNetwork.getFlaggedUsers((flags) => {
|
SocialNetwork.getFlaggedUsers((flags) => {
|
||||||
const hex = Key.toNostrHexAddress(this.props.id);
|
const reported = flags?.has(Key.toNostrHexAddress(id) as string);
|
||||||
const reported = hex && flags?.has(hex);
|
setIsReported(!!reported);
|
||||||
this.setState({ reported });
|
|
||||||
});
|
});
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
setHover(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setHover(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onButtonClick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const newValue = !isReported;
|
||||||
|
if (window.confirm(newValue ? 'Publicly report this user?' : 'Unreport user?')) {
|
||||||
|
const hex = Key.toNostrHexAddress(id);
|
||||||
|
hex && SocialNetwork.flag(hex, newValue);
|
||||||
|
onClick?.(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let buttonText;
|
||||||
|
if (isReported && hover) {
|
||||||
|
buttonText = hoverAction;
|
||||||
|
} else if (isReported && !hover) {
|
||||||
|
buttonText = actionDone;
|
||||||
|
} else {
|
||||||
|
buttonText = action;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`${cls || key} ${isReported ? activeClass : ''} ${className || ''}`}
|
||||||
|
onClick={onButtonClick}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{t(buttonText)} {showName ? <Name pub={id} hideBadge={true} /> : ''}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Report;
|
export default Report;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Link } from 'preact-router';
|
import { Link } from 'preact-router';
|
||||||
|
|
||||||
import Component from '../BaseComponent';
|
import Show from '@/components/helpers/Show.tsx';
|
||||||
|
import { RouteProps } from '@/views/types.ts';
|
||||||
|
|
||||||
import Follow from '../components/buttons/Follow';
|
import Follow from '../components/buttons/Follow';
|
||||||
import Header from '../components/Header';
|
import Header from '../components/Header';
|
||||||
import Avatar from '../components/user/Avatar';
|
import Avatar from '../components/user/Avatar';
|
||||||
@ -10,110 +12,98 @@ import Helpers from '../utils/Helpers.tsx';
|
|||||||
|
|
||||||
const IRIS_INFO_ACCOUNT = 'npub1wnwwcv0a8wx0m9stck34ajlwhzuua68ts8mw3kjvspn42dcfyjxs4n95l8';
|
const IRIS_INFO_ACCOUNT = 'npub1wnwwcv0a8wx0m9stck34ajlwhzuua68ts8mw3kjvspn42dcfyjxs4n95l8';
|
||||||
|
|
||||||
class About extends Component {
|
const About: React.FC<RouteProps> = () => (
|
||||||
render() {
|
<>
|
||||||
return (
|
<Header />
|
||||||
<>
|
<div className="main-view prose">
|
||||||
<Header />
|
<div className="px-2 md:px-4 py-2">
|
||||||
<div className="main-view prose" id="settings">
|
<h2 className="mt-0">{t('about')}</h2>
|
||||||
<div className="px-2 md:px-4 py-2">
|
<p>Iris is like the social networking apps we're used to, but better:</p>
|
||||||
<h2 className="mt-0">{t('about')}</h2>
|
<ul>
|
||||||
<p>Iris is like the social networking apps we're used to, but better:</p>
|
<li>
|
||||||
|
<b>Accessible.</b> No phone number or signup is required. Just type in your name or
|
||||||
|
alias and go!
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Secure.</b> It's open source. You can verify that your data stays safe.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Always available.</b> It works offline-first and is not dependent on any single
|
||||||
|
centrally managed server. Users can even connect directly to each other.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<Show when={!Helpers.isStandalone()}>
|
||||||
|
<h3>Versions</h3>
|
||||||
|
<p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<b>Accessible.</b> No phone number or signup is required. Just type in your name or
|
<a target="_blank" href="https://iris.to">
|
||||||
alias and go!
|
iris.to
|
||||||
|
</a>{' '}
|
||||||
|
(web)
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<b>Secure.</b> It's open source. You can verify that your data stays safe.
|
<a target="_blank" href="https://github.com/irislib/iris-messenger/releases/latest">
|
||||||
|
Desktop
|
||||||
|
</a>{' '}
|
||||||
|
(macOS, Windows, Linux)
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<b>Always available.</b> It works offline-first and is not dependent on any single
|
<a
|
||||||
centrally managed server. Users can even connect directly to each other.
|
target="_blank"
|
||||||
|
href="https://apps.apple.com/app/iris-the-nostr-client/id1665849007"
|
||||||
|
>
|
||||||
|
iOS
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" href="https://play.google.com/store/apps/details?id=to.iris.twa">
|
||||||
|
Android
|
||||||
|
</a>{' '}
|
||||||
|
(
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/irislib/iris-messenger/releases/tag/jan2023"
|
||||||
|
>
|
||||||
|
apk
|
||||||
|
</a>
|
||||||
|
)
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</p>
|
||||||
|
</Show>
|
||||||
|
|
||||||
{!Helpers.isStandalone() && (
|
<h3>Iris docs</h3>
|
||||||
<>
|
<p>
|
||||||
<h3>Versions</h3>
|
Visit Iris <a href="https://docs.iris.to">docs</a> for features, explanations and
|
||||||
<p>
|
troubleshooting.
|
||||||
<ul>
|
</p>
|
||||||
<li>
|
|
||||||
<a target="_blank" href="https://iris.to">
|
|
||||||
iris.to
|
|
||||||
</a>{' '}
|
|
||||||
(web)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://github.com/irislib/iris-messenger/releases/latest"
|
|
||||||
>
|
|
||||||
Desktop
|
|
||||||
</a>{' '}
|
|
||||||
(macOS, Windows, Linux)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://apps.apple.com/app/iris-the-nostr-client/id1665849007"
|
|
||||||
>
|
|
||||||
iOS
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://play.google.com/store/apps/details?id=to.iris.twa"
|
|
||||||
>
|
|
||||||
Android
|
|
||||||
</a>{' '}
|
|
||||||
(
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://github.com/irislib/iris-messenger/releases/tag/jan2023"
|
|
||||||
>
|
|
||||||
apk
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<h3>Iris docs</h3>
|
<h3>Privacy</h3>
|
||||||
<p>
|
<p>{t('application_security_warning')}</p>
|
||||||
Visit Iris <a href="https://docs.iris.to">docs</a> for features, explanations and
|
|
||||||
troubleshooting.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Privacy</h3>
|
<h3>Follow</h3>
|
||||||
<p>{t('application_security_warning')}</p>
|
<div className="flex flex-row items-center w-full justify-between">
|
||||||
|
<Link href={`/${IRIS_INFO_ACCOUNT}`} className="flex flex-row items-center gap-2">
|
||||||
<h3>Follow</h3>
|
<Avatar str={IRIS_INFO_ACCOUNT} width={40} />
|
||||||
<div className="flex flex-row items-center w-full justify-between">
|
<Name pub={IRIS_INFO_ACCOUNT} placeholder="Iris" />
|
||||||
<Link href={`/${IRIS_INFO_ACCOUNT}`} className="flex flex-row items-center gap-2">
|
</Link>
|
||||||
<Avatar str={IRIS_INFO_ACCOUNT} width={40} />
|
<Follow className="btn btn-neutral btn-sm" id={IRIS_INFO_ACCOUNT} />
|
||||||
<Name pub={IRIS_INFO_ACCOUNT} placeholder="Iris" />
|
|
||||||
</Link>
|
|
||||||
<Follow className="btn btn-neutral btn-sm" id={IRIS_INFO_ACCOUNT} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://t.me/irismessenger">Telegram</a> channel.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Released under MIT license. Code:{' '}
|
|
||||||
<a href="https://github.com/irislib/iris-messenger">Github</a>.
|
|
||||||
</p>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
);
|
<p>
|
||||||
}
|
<a href="https://t.me/irismessenger">Telegram</a> channel.
|
||||||
}
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Released under MIT license. Code:{' '}
|
||||||
|
<a href="https://github.com/irislib/iris-messenger">Github</a>.
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
export default About;
|
export default About;
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
import { route } from 'preact-router';
|
import { route } from 'preact-router';
|
||||||
|
|
||||||
import Component from '../BaseComponent';
|
import { RouteProps } from '@/views/types.ts';
|
||||||
|
|
||||||
import Upload from '../components/buttons/Upload';
|
import Upload from '../components/buttons/Upload';
|
||||||
import Header from '../components/Header';
|
import Header from '../components/Header';
|
||||||
import SafeImg from '../components/SafeImg';
|
import SafeImg from '../components/SafeImg';
|
||||||
@ -16,172 +18,153 @@ const explainers = {
|
|||||||
nip05: 'Nostr address (nip05)',
|
nip05: 'Nostr address (nip05)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class EditProfile extends Component {
|
const EditProfile: React.FC<RouteProps> = () => {
|
||||||
constructor(props) {
|
const [profile, setProfile] = useState({});
|
||||||
super(props);
|
const [newFieldName, setNewFieldName] = useState('');
|
||||||
this.state = {
|
const [newFieldValue, setNewFieldValue] = useState('');
|
||||||
profile: {},
|
const [edited, setEdited] = useState(false);
|
||||||
newFieldName: '',
|
|
||||||
newFieldValue: '',
|
|
||||||
edited: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
useEffect(() => {
|
||||||
SocialNetwork.getProfile(Key.getPubKey(), (p) => {
|
return SocialNetwork.getProfile(Key.getPubKey(), (p) => {
|
||||||
if (!this.state.edited && Object.keys(this.state.profile).length === 0) {
|
if (!edited && Object.keys(profile).length === 0) {
|
||||||
delete p['created_at'];
|
delete p['created_at'];
|
||||||
this.setState({
|
setProfile(p);
|
||||||
profile: p,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}, [profile, edited]);
|
||||||
|
|
||||||
saveOnChange = debounce(() => {
|
const saveOnChange = debounce(() => {
|
||||||
const profile = this.state.profile;
|
const trimmedProfile = { ...profile };
|
||||||
Object.keys(profile).forEach((key) => {
|
Object.keys(trimmedProfile).forEach((key) => {
|
||||||
if (typeof profile[key] === 'string') {
|
if (typeof trimmedProfile[key] === 'string') {
|
||||||
profile[key] = profile[key].trim();
|
trimmedProfile[key] = trimmedProfile[key].trim();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
SocialNetwork.setMetadata(profile);
|
SocialNetwork.setMetadata(trimmedProfile);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
setProfileAttribute = (key, value) => {
|
const setProfileAttribute = (key, value) => {
|
||||||
key = key.trim();
|
key = key.trim();
|
||||||
const profile = Object.assign({}, this.state.profile);
|
const updatedProfile = { ...profile };
|
||||||
if (value) {
|
if (value) {
|
||||||
profile[key] = value;
|
updatedProfile[key] = value;
|
||||||
} else {
|
} else {
|
||||||
delete profile[key];
|
delete updatedProfile[key];
|
||||||
}
|
}
|
||||||
this.setState({ profile, edited: true });
|
setProfile(updatedProfile);
|
||||||
this.saveOnChange();
|
setEdited(true);
|
||||||
|
saveOnChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = (event) => {
|
const handleSubmit = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
SocialNetwork.setMetadata(this.state.profile);
|
SocialNetwork.setMetadata(profile);
|
||||||
const myPub = Key.toNostrBech32Address(Key.getPubKey(), 'npub');
|
const myPub = Key.toNostrBech32Address(Key.getPubKey(), 'npub');
|
||||||
route('/' + myPub);
|
route('/' + myPub);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAddField = (event) => {
|
const handleAddField = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const fieldName = this.state.newFieldName;
|
if (newFieldName && newFieldValue) {
|
||||||
const fieldValue = this.state.newFieldValue;
|
setProfileAttribute(newFieldName, newFieldValue);
|
||||||
if (fieldName && fieldValue) {
|
setNewFieldName('');
|
||||||
this.setProfileAttribute(fieldName, fieldValue);
|
setNewFieldValue('');
|
||||||
this.setState({ newFieldName: '', newFieldValue: '' });
|
SocialNetwork.setMetadata(profile);
|
||||||
SocialNetwork.setMetadata(this.state.profile);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
const fields = ['name', 'picture', 'about', 'banner', 'website', 'lud16', 'nip05'];
|
||||||
const fields = ['name', 'picture', 'about', 'banner', 'website', 'lud16', 'nip05'];
|
Object.keys(profile).forEach((key) => {
|
||||||
// add other possible fields from profile
|
if (!fields.includes(key)) {
|
||||||
Object.keys(this.state.profile).forEach((key) => {
|
fields.push(key);
|
||||||
if (!fields.includes(key)) {
|
}
|
||||||
fields.push(key);
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header />
|
<Header />
|
||||||
<div class="main-view" id="settings">
|
<div class="mx-2 md:mx-4">
|
||||||
<div class="centered-container prose">
|
<div class="centered-container prose">
|
||||||
<h3>{t('edit_profile')}</h3>
|
<h3>{t('edit_profile')}</h3>
|
||||||
<form onSubmit={(e) => this.handleSubmit(e)}>
|
<form onSubmit={handleSubmit}>
|
||||||
{fields.map((field) => {
|
{fields.map((field) => {
|
||||||
const val = this.state.profile[field];
|
const val = profile[field];
|
||||||
const isString = typeof val === 'string' || typeof val === 'undefined';
|
const isString = typeof val === 'string' || typeof val === 'undefined';
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
<label htmlFor={field}>{explainers[field] || field}:</label>
|
<label htmlFor={field}>{explainers[field] || field}:</label>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input
|
||||||
className="input w-full"
|
className="input w-full"
|
||||||
type="text"
|
type="text"
|
||||||
id={field}
|
id={field}
|
||||||
disabled={!isString}
|
disabled={!isString}
|
||||||
value={isString ? val || '' : JSON.stringify(val)}
|
value={isString ? val || '' : JSON.stringify(val)}
|
||||||
onInput={(e) =>
|
onInput={(e: any) => isString && setProfileAttribute(field, e.target.value)}
|
||||||
isString &&
|
/>
|
||||||
this.setProfileAttribute(field, (e.target as HTMLInputElement).value)
|
{field === 'lud16' && !val && (
|
||||||
}
|
<p>
|
||||||
/>
|
<small>{t('install_lightning_wallet_prompt')}</small>
|
||||||
{field === 'lud16' && !val && (
|
</p>
|
||||||
|
)}
|
||||||
|
{(field === 'picture' || field === 'banner') && (
|
||||||
|
<>
|
||||||
<p>
|
<p>
|
||||||
<small>{t('install_lightning_wallet_prompt')}</small>
|
<Upload onUrl={(url) => setProfileAttribute(field, url)} />
|
||||||
</p>
|
</p>
|
||||||
)}
|
{val && (
|
||||||
{field === 'picture' || field === 'banner' ? (
|
|
||||||
<>
|
|
||||||
<p>
|
<p>
|
||||||
<Upload onUrl={(url) => this.setProfileAttribute(field, url)} />
|
<SafeImg key={val} src={val} />
|
||||||
</p>
|
</p>
|
||||||
{val && (
|
)}
|
||||||
<p>
|
</>
|
||||||
<SafeImg key={val} src={val} />
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
);
|
||||||
</>
|
})}
|
||||||
) : null}
|
<p>
|
||||||
</p>
|
<button className="btn btn-primary" type="submit">
|
||||||
);
|
Save
|
||||||
})}
|
</button>
|
||||||
<p>
|
</p>
|
||||||
<button className="btn btn-primary" type="submit">
|
</form>
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h4>Add new field</h4>
|
<h4>Add new field</h4>
|
||||||
<form onSubmit={(e) => this.handleAddField(e)}>
|
<form onSubmit={handleAddField}>
|
||||||
<p>
|
<p>
|
||||||
<label htmlFor="newFieldName">Field name:</label>
|
<label htmlFor="newFieldName">Field name:</label>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input
|
||||||
value={this.state.newFieldName}
|
value={newFieldName}
|
||||||
type="text"
|
type="text"
|
||||||
id="newFieldName"
|
id="newFieldName"
|
||||||
className="input w-full"
|
className="input w-full"
|
||||||
placeholder={t('field_name')}
|
placeholder={t('field_name')}
|
||||||
onInput={(e) =>
|
onInput={(e: any) => setNewFieldName(e.target.value)}
|
||||||
this.setState({
|
/>
|
||||||
newFieldName: (e.target as HTMLInputElement).value,
|
</p>
|
||||||
})
|
<p>
|
||||||
}
|
<label htmlFor="newFieldValue">Field value:</label>
|
||||||
/>
|
<br />
|
||||||
</p>
|
<input
|
||||||
<p>
|
value={newFieldValue}
|
||||||
<label htmlFor="newFieldValue">Field value:</label>
|
type="text"
|
||||||
<br />
|
id="newFieldValue"
|
||||||
<input
|
className="input w-full"
|
||||||
value={this.state.newFieldValue}
|
placeholder={t('field_value')}
|
||||||
type="text"
|
onInput={(e: any) => setNewFieldValue(e.target.value)}
|
||||||
id="newFieldValue"
|
/>
|
||||||
className="input w-full"
|
</p>
|
||||||
placeholder={t('field_value')}
|
<p>
|
||||||
onInput={(e) =>
|
<button className="btn btn-primary" type="submit">
|
||||||
this.setState({
|
Add new attribute
|
||||||
newFieldValue: (e.target as HTMLInputElement).value,
|
</button>
|
||||||
})
|
</p>
|
||||||
}
|
</form>
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<button className="btn btn-primary" type="submit">
|
|
||||||
Add new attribute
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
</>
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default EditProfile;
|
||||||
|
@ -1,77 +1,73 @@
|
|||||||
import Component from '../BaseComponent';
|
import { RouteProps } from '@/views/types.ts';
|
||||||
|
|
||||||
import Header from '../components/Header';
|
import Header from '../components/Header';
|
||||||
import { translate as t } from '../translations/Translation.mjs';
|
import { translate as t } from '../translations/Translation.mjs';
|
||||||
|
|
||||||
class Subscribe extends Component {
|
const Subscribe: React.FC<RouteProps> = () => (
|
||||||
render() {
|
<>
|
||||||
return (
|
<Header />
|
||||||
<>
|
<div className="main-view" id="settings">
|
||||||
<Header />
|
<div className="centered-container mobile-padding15">
|
||||||
<div className="main-view" id="settings">
|
<h2>{t('subscribe')}</h2>
|
||||||
<div className="centered-container mobile-padding15">
|
<h3>Iris Supporter</h3>
|
||||||
<h2>{t('subscribe')}</h2>
|
<p>Support open source development and get extra features!</p>
|
||||||
<h3>Iris Supporter</h3>
|
<p>
|
||||||
<p>Support open source development and get extra features!</p>
|
<ul>
|
||||||
<p>
|
<li>Iris Supporter Badge</li>
|
||||||
<ul>
|
<li>Purple checkmark on Iris</li>
|
||||||
<li>Iris Supporter Badge</li>
|
<li>Iris Supporters' private group chat</li>
|
||||||
<li>Purple checkmark on Iris</li>
|
{/*
|
||||||
<li>High-quality automatic translations (via deepl.com)</li>
|
|
||||||
<li>Iris Supporters' private group chat</li>
|
|
||||||
{/*
|
|
||||||
:D
|
:D
|
||||||
<li>Email-DM bridge for your Iris address</li>
|
<li>Email-DM bridge for your Iris address</li>
|
||||||
<li>Bitcoin Lightning proxy for your Iris address</li>
|
<li>Bitcoin Lightning proxy for your Iris address</li>
|
||||||
<li>Custom themes for your profile page</li>
|
<li>Custom themes for your profile page</li>
|
||||||
<li>Profile view statistics</li>
|
<li>Profile view statistics</li>
|
||||||
*/}
|
*/}
|
||||||
<li>More features to come!</li>
|
<li>More features to come!</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<input
|
<input
|
||||||
defaultChecked={true}
|
defaultChecked={true}
|
||||||
type="radio"
|
type="radio"
|
||||||
id="subscription_annually"
|
id="subscription_annually"
|
||||||
name="subscription"
|
name="subscription"
|
||||||
value="1"
|
value="1"
|
||||||
/>
|
/>
|
||||||
<label htmlFor="subscription_annually">
|
<label htmlFor="subscription_annually">
|
||||||
<b>8 € / month</b> charged annually (96 € / year)
|
<b>8 € / month</b> charged annually (96 € / year)
|
||||||
</label>
|
</label>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<input type="radio" id="subscription_monthly" name="subscription" value="2" />
|
<input type="radio" id="subscription_monthly" name="subscription" value="2" />
|
||||||
<label htmlFor="subscription_monthly">
|
<label htmlFor="subscription_monthly">
|
||||||
<b>10 € / month</b> charged monthly (120 € / year)
|
<b>10 € / month</b> charged monthly (120 € / year)
|
||||||
</label>
|
</label>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<button className="btn btn-primary">Subscribe</button>
|
<button className="btn btn-primary">Subscribe</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>Iris Titan</h3>
|
<h3>Iris Titan</h3>
|
||||||
<p>
|
<p>
|
||||||
True Mighty Titan status. Lifetime Iris Purple access, plus:
|
True Mighty Titan status. Lifetime Iris Purple access, plus:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Iris Titan Badge</li>
|
<li>Iris Titan Badge</li>
|
||||||
<li>Iris Titans private group chat</li>
|
<li>Iris Titans private group chat</li>
|
||||||
<li>Priority support</li>
|
<li>Priority support</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>1000 €</b> one-time payment.
|
<b>1000 €</b> one-time payment.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<button className="btn btn-primary">Subscribe</button>
|
<button className="btn btn-primary">Subscribe</button>
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Subscribe;
|
export default Subscribe;
|
||||||
|
3
src/js/views/types.ts
Normal file
3
src/js/views/types.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export type RouteProps = {
|
||||||
|
path: string;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user