feat: display banner in profile #53
@ -8,7 +8,7 @@ export default function FollowButton(props) {
|
|||||||
const publiser = useEventPublisher();
|
const publiser = useEventPublisher();
|
||||||
const follows = useSelector(s => s.login.follows);
|
const follows = useSelector(s => s.login.follows);
|
||||||
let isFollowing = follows?.includes(pubkey) ?? false;
|
let isFollowing = follows?.includes(pubkey) ?? false;
|
||||||
const baseClassName = isFollowing ? `btn btn-warn` : `btn btn-success`
|
const baseClassName = isFollowing ? `btn btn-warn follow-button` : `btn btn-success follow-button`
|
||||||
const className = props.className ? `${baseClassName} ${props.className}` : `${baseClassName}`;
|
const className = props.className ? `${baseClassName} ${props.className}` : `${baseClassName}`;
|
||||||
|
|
||||||
async function follow(pubkey) {
|
async function follow(pubkey) {
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
.profile {
|
.profile {
|
||||||
align-items: flex-start;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile > div:last-child {
|
.profile .banner {
|
||||||
margin-left: 10px;
|
width: 100%;
|
||||||
|
height: 210px;
|
||||||
|
margin-bottom: -80px;
|
||||||
|
object-fit: cover;
|
||||||
|
mask-image: linear-gradient(to bottom, var(--bg-color), rgba(0,0,0,0));
|
||||||
|
-webkit-mask-image: linear-gradient(to bottom, var(--bg-color), rgba(0,0,0,0));
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 720px) {
|
||||||
|
.profile .banner {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 720px;
|
||||||
|
height: 300px;
|
||||||
|
margin-bottom: -120px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile p {
|
.profile p {
|
||||||
@ -14,14 +29,17 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.profile .avatar-wrapper {
|
.profile .avatar-wrapper {
|
||||||
margin: auto 10px;
|
align-self: flex-start;
|
||||||
|
z-index: 1;
|
||||||
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .avatar {
|
.profile .avatar {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
height: 256px;
|
height: 210px;
|
||||||
width: 256px;
|
width: 210px;
|
||||||
background-image: var(--img-url), var(--gray-gradient);
|
background-image: var(--img-url), var(--gray-gradient);
|
||||||
border: 4px solid transparent;
|
border: 4px solid transparent;
|
||||||
background-origin: border-box;
|
background-origin: border-box;
|
||||||
@ -38,23 +56,40 @@
|
|||||||
background-image: var(--img-url), var(--nostrplebs-gradient);
|
background-image: var(--img-url), var(--nostrplebs-gradient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile .name {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .name h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.profile .details {
|
.profile .details {
|
||||||
margin-top: auto;
|
max-width: 680px;
|
||||||
margin-bottom: auto;
|
width: 100%;
|
||||||
overflow: hidden;
|
}
|
||||||
|
|
||||||
|
.profile .details p {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .details a {
|
||||||
|
color: var(--highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .website {
|
.profile .website {
|
||||||
padding-left: 0;
|
|
||||||
color: var(--highlight);
|
color: var(--highlight);
|
||||||
margin-bottom: 2px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .lnurl {
|
.profile .lnurl {
|
||||||
padding-left: 0;
|
color: var(--highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .btn-icon {
|
.profile .btn-icon {
|
||||||
|
color: var(--font-color);
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
@ -64,8 +99,9 @@
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile a {
|
.profile .lnurl {
|
||||||
color: var(--highlight);
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .lnurl::before {
|
.profile .lnurl::before {
|
||||||
@ -73,22 +109,44 @@
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: 720px) {
|
.profile .details-wrapper {
|
||||||
.profile {
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
}
|
justify-content: space-between;
|
||||||
.profile > div:last-child {
|
position: relative;
|
||||||
margin: 5px 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: 360px) {
|
.profile .copy .body { font-size: 12px }
|
||||||
.profile .name { flex-direction: column; }
|
|
||||||
.profile .name .btn {
|
@media (min-width: 360px) {
|
||||||
margin-top: 5px;
|
.profile .copy .body { font-size: 14px }
|
||||||
|
.profile .details-wrapper, .profile .avatar-wrapper { margin-left: 21px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 720px) {
|
||||||
|
.profile .details-wrapper, .profile .avatar-wrapper { margin-left: 30px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .follow-button {
|
||||||
|
position: absolute;
|
||||||
|
top: -30px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .message-button {
|
||||||
|
position: absolute;
|
||||||
|
top: -30px;
|
||||||
|
right: 74px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .no-banner .follow-button {
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
.profile .no-banner .message-button {
|
||||||
|
right: 54px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
@ -110,3 +168,37 @@
|
|||||||
.tab.active {
|
.tab.active {
|
||||||
border-bottom: 3px solid var(--highlight);
|
border-bottom: 3px solid var(--highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile .no-banner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .no-banner .avatar {
|
||||||
|
height: 256px;
|
||||||
|
width: 256px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .no-banner .avatar-wrapper, .profile .no-banner .details-wrapper {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 720px) {
|
||||||
|
.profile .no-banner {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-top: 21px;
|
||||||
|
}
|
||||||
|
.profile .no-banner .avatar-wrapper {
|
||||||
|
margin: auto 10px;
|
||||||
|
}
|
||||||
|
.profile .no-banner .details-wrapper {
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 21px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -45,35 +45,20 @@ export default function ProfilePage() {
|
|||||||
setTab(ProfileTab.Notes);
|
setTab(ProfileTab.Notes);
|
||||||
}, [params]);
|
}, [params]);
|
||||||
|
|
||||||
function details() {
|
function username() {
|
||||||
const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || "");
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="name">
|
||||||
<div className="flex name">
|
<h2>{user?.display_name || user?.name || 'Nostrich'}</h2>
|
||||||
<div className="f-grow f-ellipsis">
|
|
||||||
<h2>{user?.display_name || user?.name}</h2></div>
|
|
||||||
<div className="flex">
|
|
||||||
{isMe ? (
|
|
||||||
<div className="btn btn-icon" onClick={() => navigate("/settings")}>
|
|
||||||
<FontAwesomeIcon icon={faGear} size="lg" />
|
|
||||||
</div>
|
|
||||||
) : <>
|
|
||||||
<div className="btn mr5" onClick={() => navigate(`/messages/${hexToBech32("npub", id)}`)}>
|
|
||||||
<FontAwesomeIcon icon={faEnvelope} size="lg" />
|
|
||||||
</div>
|
|
||||||
<FollowButton pubkey={id} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<div className="f-grow">
|
|
||||||
|
|
||||||
<Copy text={params.id} />
|
<Copy text={params.id} />
|
||||||
{user?.nip05 && <Nip05 name={name} domain={domain} isVerified={isVerified} couldNotVerify={couldNotVerify} />}
|
{user?.nip05 && <Nip05 name={name} domain={domain} isVerified={isVerified} couldNotVerify={couldNotVerify} />}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
function bio() {
|
||||||
|
const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || "");
|
||||||
|
return (
|
||||||
|
<div className="details">
|
||||||
<p>{about}</p>
|
<p>{about}</p>
|
||||||
|
|
||||||
{user?.website && (
|
{user?.website && (
|
||||||
@ -89,7 +74,7 @@ export default function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div> : null}
|
</div> : null}
|
||||||
<LNURLTip svc={lnurl} show={showLnQr} onClose={() => setShowLnQr(false)} />
|
<LNURLTip svc={lnurl} show={showLnQr} onClose={() => setShowLnQr(false)} />
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,16 +100,50 @@ export default function ProfilePage() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function avatar() {
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<div className="profile flex">
|
|
||||||
<div className="avatar-wrapper">
|
<div className="avatar-wrapper">
|
||||||
<div style={{ '--img-url': backgroundImage }} className="avatar" data-domain={isVerified ? domain : ''}>
|
<div style={{ '--img-url': backgroundImage }} className="avatar" data-domain={isVerified ? domain : ''}>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="f-grow details">
|
)
|
||||||
{details()}
|
}
|
||||||
|
|
||||||
|
function userDetails() {
|
||||||
|
return (
|
||||||
|
<div className="details-wrapper">
|
||||||
|
{username()}
|
||||||
|
{isMe ? (
|
||||||
|
<div className="btn btn-icon follow-button" onClick={() => navigate("/settings")}>
|
||||||
|
<FontAwesomeIcon icon={faGear} size="lg" />
|
||||||
</div>
|
</div>
|
||||||
|
) : <>
|
||||||
|
<div className="btn message-button" onClick={() => navigate(`/messages/${hexToBech32("npub", id)}`)}>
|
||||||
|
<FontAwesomeIcon icon={faEnvelope} size="lg" />
|
||||||
|
</div>
|
||||||
|
<FollowButton pubkey={id} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{bio()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="profile flex">
|
||||||
|
{user?.banner && <img alt="banner" className="banner" src={user.banner} /> }
|
||||||
|
{user?.banner ? (
|
||||||
|
<>
|
||||||
|
{avatar()}
|
||||||
|
{userDetails()}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="no-banner">
|
||||||
|
{avatar()}
|
||||||
|
{userDetails()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="tabs">
|
<div className="tabs">
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,20 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings .avatar .edit {
|
.settings .banner {
|
||||||
|
width: 300px;
|
||||||
|
height: 150px;
|
||||||
|
background-size: cover;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings .image-settings {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings .avatar .edit, .settings .banner .edit {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -28,6 +28,7 @@ export default function SettingsPage(props) {
|
|||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [displayName, setDisplayName] = useState("");
|
const [displayName, setDisplayName] = useState("");
|
||||||
const [picture, setPicture] = useState("");
|
const [picture, setPicture] = useState("");
|
||||||
|
const [banner, setBanner] = useState("");
|
||||||
const [about, setAbout] = useState("");
|
const [about, setAbout] = useState("");
|
||||||
const [website, setWebsite] = useState("");
|
const [website, setWebsite] = useState("");
|
||||||
const [nip05, setNip05] = useState("");
|
const [nip05, setNip05] = useState("");
|
||||||
@ -35,11 +36,14 @@ export default function SettingsPage(props) {
|
|||||||
const [lud16, setLud16] = useState("");
|
const [lud16, setLud16] = useState("");
|
||||||
const [newRelay, setNewRelay] = useState("");
|
const [newRelay, setNewRelay] = useState("");
|
||||||
|
|
||||||
|
const avatarPicture = picture.length === 0 ? Nostrich : picture
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
setName(user.name ?? "");
|
setName(user.name ?? "");
|
||||||
setDisplayName(user.display_name ?? "")
|
setDisplayName(user.display_name ?? "")
|
||||||
setPicture(user.picture ?? "");
|
setPicture(user.picture ?? "");
|
||||||
|
setBanner(user.banner ?? "");
|
||||||
setAbout(user.about ?? "");
|
setAbout(user.about ?? "");
|
||||||
setWebsite(user.website ?? "");
|
setWebsite(user.website ?? "");
|
||||||
setNip05(user.nip05 ?? "");
|
setNip05(user.nip05 ?? "");
|
||||||
@ -56,6 +60,7 @@ export default function SettingsPage(props) {
|
|||||||
display_name: displayName,
|
display_name: displayName,
|
||||||
about,
|
about,
|
||||||
picture,
|
picture,
|
||||||
|
banner,
|
||||||
website,
|
website,
|
||||||
nip05,
|
nip05,
|
||||||
lud16
|
lud16
|
||||||
@ -86,17 +91,26 @@ export default function SettingsPage(props) {
|
|||||||
publisher.broadcast(ev);
|
publisher.broadcast(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setNewAvatar() {
|
async function uploadFile() {
|
||||||
let file = await openFile();
|
let file = await openFile();
|
||||||
console.log(file);
|
console.log(file);
|
||||||
let rsp = await VoidUpload(file);
|
let rsp = await VoidUpload(file);
|
||||||
if (!rsp) {
|
if (!rsp) {
|
||||||
throw "Upload failed, please try again later";
|
throw "Upload failed, please try again later";
|
||||||
}
|
}
|
||||||
console.log(rsp);
|
return rsp
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setNewAvatar() {
|
||||||
|
const rsp = await uploadFile()
|
||||||
setPicture(rsp.metadata.url ?? `https://void.cat/d/${rsp.id}`)
|
setPicture(rsp.metadata.url ?? `https://void.cat/d/${rsp.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setNewBanner() {
|
||||||
|
const rsp = await uploadFile()
|
||||||
|
setBanner(rsp.metadata.url ?? `https://void.cat/d/${rsp.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
async function saveRelays() {
|
async function saveRelays() {
|
||||||
let ev = await publisher.saveRelays();
|
let ev = await publisher.saveRelays();
|
||||||
publisher.broadcast(ev);
|
publisher.broadcast(ev);
|
||||||
@ -175,11 +189,20 @@ export default function SettingsPage(props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>Settings</h1>
|
<h1>Settings</h1>
|
||||||
<div className="flex f-center">
|
<div className="flex f-center image-settings">
|
||||||
<div style={{ backgroundImage: `url(${picture.length === 0 ? Nostrich : picture})` }} className="avatar">
|
<div>
|
||||||
|
<h2>Avatar</h2>
|
||||||
|
<div style={{ backgroundImage: `url(${avatarPicture})` }} className="avatar">
|
||||||
<div className="edit" onClick={() => setNewAvatar()}>Edit</div>
|
<div className="edit" onClick={() => setNewAvatar()}>Edit</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Header</h2>
|
||||||
|
<div style={{ backgroundImage: `url(${banner.length === 0 ? avatarPicture : banner})` }} className="banner">
|
||||||
|
<div className="edit" onClick={() => setNewBanner()}>Edit</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{editor()}
|
{editor()}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user