mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 07:33:22 +00:00
redesign EditProfile (#446)
Co-authored-by: BlowaterNostr <blowater.nostr@proton.me>
This commit is contained in:
parent
728675690b
commit
7044128342
@ -309,12 +309,6 @@ type AppProps = {
|
||||
};
|
||||
|
||||
export class AppComponent extends Component<AppProps> {
|
||||
events = this.props.eventBus.onChange();
|
||||
|
||||
componentWillUnmount() {
|
||||
this.events.close();
|
||||
}
|
||||
|
||||
render(props: AppProps) {
|
||||
const t = Date.now();
|
||||
const model = props.model;
|
||||
@ -404,22 +398,11 @@ export class AppComponent extends Component<AppProps> {
|
||||
);
|
||||
}
|
||||
|
||||
const final = (
|
||||
<div class={`h-screen w-full flex`}>
|
||||
<NavBar
|
||||
publicKey={app.ctx.publicKey}
|
||||
profile={app.database.getProfilesByPublicKey(myAccountCtx.publicKey)}
|
||||
emit={app.eventBus.emit}
|
||||
installPrompt={props.installPrompt}
|
||||
currentRelay={model.currentRelay}
|
||||
activeNav={model.navigationModel.activeNav}
|
||||
pool={app.pool}
|
||||
/>
|
||||
|
||||
let profileEditorNode: VNode | undefined;
|
||||
if (model.navigationModel.activeNav == "Profile") {
|
||||
profileEditorNode = (
|
||||
<div
|
||||
class={`h-full px-[3rem] sm:px-4 bg-[${SecondaryBackgroundColor}] flex-1 overflow-auto${
|
||||
model.navigationModel.activeNav == "Profile" ? " block" : " hidden"
|
||||
}`}
|
||||
class={`h-full px-[3rem] sm:px-4 bg-[${SecondaryBackgroundColor}] flex-1 overflow-auto block`}
|
||||
>
|
||||
<div
|
||||
class={`max-w-[35rem] h-full m-auto`}
|
||||
@ -432,6 +415,21 @@ export class AppComponent extends Component<AppProps> {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const final = (
|
||||
<div class={`h-screen w-full flex`}>
|
||||
<NavBar
|
||||
publicKey={app.ctx.publicKey}
|
||||
profile={app.database.getProfilesByPublicKey(myAccountCtx.publicKey)}
|
||||
emit={app.eventBus.emit}
|
||||
installPrompt={props.installPrompt}
|
||||
currentRelay={model.currentRelay}
|
||||
activeNav={model.navigationModel.activeNav}
|
||||
pool={app.pool}
|
||||
/>
|
||||
{profileEditorNode}
|
||||
{publicNode}
|
||||
{dmVNode}
|
||||
{aboutNode}
|
||||
|
@ -1,24 +1,17 @@
|
||||
import { createRef, Fragment, h } from "https://esm.sh/preact@10.17.1";
|
||||
import {
|
||||
CenterClass,
|
||||
DividerClass,
|
||||
InputClass,
|
||||
LinearGradientsClass,
|
||||
NoOutlineClass,
|
||||
} from "./components/tw.ts";
|
||||
import { createRef, h } from "https://esm.sh/preact@10.17.1";
|
||||
import { ProfileData } from "../features/profile.ts";
|
||||
import {
|
||||
DividerBackgroundColor,
|
||||
ErrorColor,
|
||||
HintLinkColor,
|
||||
HintTextColor,
|
||||
HoverButtonBackgroundColor,
|
||||
PlaceholderColor,
|
||||
PrimaryTextColor,
|
||||
SecondaryBackgroundColor,
|
||||
} from "./style/colors.ts";
|
||||
import { Component, ComponentChildren } from "https://esm.sh/preact@10.11.3";
|
||||
import { Component } from "https://esm.sh/preact@10.11.3";
|
||||
import { emitFunc } from "../event-bus.ts";
|
||||
import { NostrAccountContext } from "../../libs/nostr.ts/nostr.ts";
|
||||
import { UserIcon } from "./icons/user-icon.tsx";
|
||||
|
||||
export type SaveProfile = {
|
||||
type: "SaveProfile";
|
||||
@ -26,12 +19,6 @@ export type SaveProfile = {
|
||||
ctx: NostrAccountContext;
|
||||
};
|
||||
|
||||
type profileItem = {
|
||||
key: string;
|
||||
value?: string;
|
||||
hint?: ComponentChildren;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
ctx: NostrAccountContext;
|
||||
profile: ProfileData;
|
||||
@ -49,134 +36,112 @@ export class EditProfile extends Component<Props, State> {
|
||||
profileData: undefined,
|
||||
};
|
||||
|
||||
newFieldKey = createRef<HTMLInputElement>();
|
||||
newFieldValue = createRef<HTMLInputElement>();
|
||||
|
||||
componentDidMount(): void {
|
||||
this.setState({
|
||||
profileData: this.props.profile,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const profileItems: profileItem[] = [
|
||||
{
|
||||
key: "name",
|
||||
value: this.state.profileData?.name,
|
||||
},
|
||||
{
|
||||
key: "banner",
|
||||
value: this.state.profileData?.banner,
|
||||
},
|
||||
{
|
||||
key: "picture",
|
||||
value: this.state.profileData?.picture,
|
||||
hint: (
|
||||
<span class={this.styles.field.hint.text}>
|
||||
render(props: Props, state: State) {
|
||||
return (
|
||||
<form class={`py-4 bg-[${SecondaryBackgroundColor}]`} onSubmit={this.onSubmit}>
|
||||
<img
|
||||
src={`${
|
||||
this.state.profileData?.banner ||
|
||||
"https://images.unsplash.com/photo-1468581264429-2548ef9eb732"
|
||||
}`}
|
||||
class="w-full h-[300px] object-cover rounded-lg"
|
||||
/>
|
||||
<img
|
||||
src={`${this.state.profileData?.picture || "/logo.webp"}`}
|
||||
class="w-20 h-20 rounded-full mx-auto border-[3px] border-white
|
||||
object-cover
|
||||
transform -translate-y-10 bg-white"
|
||||
/>
|
||||
<div
|
||||
class={`my-4 p-4 rounded-2xl border-[2px] border-[${DividerBackgroundColor}] `}
|
||||
>
|
||||
<div class={`flex justify-start items-center gap-2`}>
|
||||
<UserIcon class="w-8 h-8 text-[#FF772B]" />
|
||||
<div
|
||||
class={`text-[${PrimaryTextColor}] text-[1.3125rem] font-not-italic font-700 leading-[1.5rem] tracking--0.21px`}
|
||||
>
|
||||
Profile
|
||||
</div>
|
||||
</div>
|
||||
<h3 class={`text-[${PrimaryTextColor}] mt-8`}>
|
||||
Name
|
||||
</h3>
|
||||
<input
|
||||
value={state.profileData?.name}
|
||||
name="name"
|
||||
onInput={(e) => this.onInput(e, "name")}
|
||||
type="text"
|
||||
class={`focus:outline-none focus:border-white w-full px-4 py-3 rounded-lg resize-y bg-transparent border-[2px] border-[${DividerBackgroundColor}] placeholder-[${PlaceholderColor}] text-[${PrimaryTextColor}] `}
|
||||
/>
|
||||
<h3 class={`text-[${PrimaryTextColor}] mt-8`}>
|
||||
Profile Image URL
|
||||
</h3>
|
||||
<input
|
||||
value={state.profileData?.picture}
|
||||
name="picture"
|
||||
onInput={(e) => this.onInput(e, "picture")}
|
||||
type="text"
|
||||
class={`focus:outline-none focus:border-white w-full px-4 py-3 rounded-lg resize-y bg-transparent border-[2px] border-[${DividerBackgroundColor}] placeholder-[${PlaceholderColor}] text-[${PrimaryTextColor}] `}
|
||||
/>
|
||||
<span class={`text-sm text-[${HintTextColor}]`}>
|
||||
You can upload your images on websites like{" "}
|
||||
<a class={this.styles.field.hint.link} href="https://nostr.build/" target="_blank">
|
||||
<a class={`text-[${HintLinkColor}]`} href="https://nostr.build/" target="_blank">
|
||||
nostr.build
|
||||
</a>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "about",
|
||||
value: this.state.profileData?.about,
|
||||
},
|
||||
{
|
||||
key: "website",
|
||||
value: this.state.profileData?.website,
|
||||
},
|
||||
];
|
||||
<h3 class={`text-[${PrimaryTextColor}] mt-8`}>
|
||||
Banner Image URL
|
||||
</h3>
|
||||
<input
|
||||
value={state.profileData?.banner}
|
||||
name="banner"
|
||||
onInput={(e) => this.onInput(e, "banner")}
|
||||
type="text"
|
||||
class={`focus:outline-none focus:border-white w-full px-4 py-3 rounded-lg resize-y bg-transparent border-[2px] border-[${DividerBackgroundColor}] placeholder-[${PlaceholderColor}] text-[${PrimaryTextColor}]`}
|
||||
/>
|
||||
<h3 class={`text-[${PrimaryTextColor}] mt-8`}>
|
||||
About
|
||||
</h3>
|
||||
<input
|
||||
placeholder={"tell us about yourself"}
|
||||
value={state.profileData?.about}
|
||||
name="about"
|
||||
onInput={(e) => this.onInput(e, "about")}
|
||||
type="text"
|
||||
class={`focus:outline-none focus:border-white w-full px-4 py-3 rounded-lg resize-y bg-transparent border-[2px] border-[${DividerBackgroundColor}] placeholder-[${PlaceholderColor}] text-[${PrimaryTextColor}] `}
|
||||
/>
|
||||
<h3 class={`text-[${PrimaryTextColor}] mt-8`}>
|
||||
Website
|
||||
</h3>
|
||||
<input
|
||||
value={state.profileData?.website}
|
||||
name="website"
|
||||
onInput={(e) => this.onInput(e, "website")}
|
||||
type="text"
|
||||
class={`focus:outline-none focus:border-white w-full px-4 py-3 rounded-lg resize-y bg-transparent border-[2px] border-[${DividerBackgroundColor}] placeholder-[${PlaceholderColor}] text-[${PrimaryTextColor}]`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
const items = profileItems.map((item) => (
|
||||
<Fragment>
|
||||
<h3 class={this.styles.field.title} style={{ textTransform: "capitalize" }}>
|
||||
{item.key}
|
||||
</h3>
|
||||
<textarea
|
||||
placeholder={item.key}
|
||||
rows={item.value?.split("\n")?.length || 1}
|
||||
value={item.value}
|
||||
onInput={(e) => this.onInput(e, item.key)}
|
||||
type="text"
|
||||
class={this.styles.field.input}
|
||||
<button
|
||||
class={`w-full p-3 rounded-lg text-[${PrimaryTextColor}] flex items-center justify-center bg-gradient-to-r from-[#FF762C] via-[#FF3A5E] to-[#FF01A9] hover:bg-gradient-to-l`}
|
||||
type="submit"
|
||||
>
|
||||
</textarea>
|
||||
{item.hint}
|
||||
</Fragment>
|
||||
));
|
||||
|
||||
return (
|
||||
<div class={this.styles.container}>
|
||||
{items}
|
||||
|
||||
<div class={this.styles.divider}></div>
|
||||
<p class={this.styles.custom.title}>Custom Fields</p>
|
||||
<span class={this.styles.custom.text}>
|
||||
Create your own custom fields, anything goes!
|
||||
</span>
|
||||
|
||||
<h3 class={this.styles.field.title}>
|
||||
Field name
|
||||
</h3>
|
||||
<input
|
||||
ref={this.newFieldKey}
|
||||
placeholder="e.g. hobbies"
|
||||
type="text"
|
||||
class={this.styles.field.input}
|
||||
/>
|
||||
<span class={this.styles.custom.error}>{this.state.newFieldKeyError}</span>
|
||||
|
||||
<h3 class={this.styles.field.title}>
|
||||
Field value
|
||||
</h3>
|
||||
<textarea
|
||||
ref={this.newFieldValue}
|
||||
placeholder="e.g. Sports, Reading, Design"
|
||||
rows={1}
|
||||
onInput={(e) => this.onInput(e)}
|
||||
type="text"
|
||||
class={this.styles.field.input}
|
||||
>
|
||||
</textarea>
|
||||
|
||||
<button class={this.styles.addButton} onClick={this.addField}>Add Field</button>
|
||||
|
||||
<div class={`${DividerClass}`}></div>
|
||||
|
||||
<button class={this.styles.submitButton} onClick={this.onSubmit}>Update Profile</button>
|
||||
</div>
|
||||
Update Profile
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
styles = {
|
||||
container: `py-4 bg-[${SecondaryBackgroundColor}]`,
|
||||
banner: {
|
||||
container: `h-72 w-full rounded-lg mb-20 relative`,
|
||||
},
|
||||
field: {
|
||||
title: `text-[${PrimaryTextColor}] mt-8`,
|
||||
input: `${InputClass}`,
|
||||
hint: {
|
||||
text: `text-sm text-[${HintTextColor}]`,
|
||||
link: `text-[${HintLinkColor}]`,
|
||||
},
|
||||
},
|
||||
addButton:
|
||||
`w-full mt-6 p-3 rounded-lg ${NoOutlineClass} text-[${PrimaryTextColor}] bg-[${DividerBackgroundColor}] hover:bg-[${HoverButtonBackgroundColor}] ${CenterClass}`,
|
||||
submitButton:
|
||||
`w-full p-3 rounded-lg ${NoOutlineClass} text-[${PrimaryTextColor}] ${CenterClass} ${LinearGradientsClass} hover:bg-gradient-to-l`,
|
||||
divider: `${DividerClass}`,
|
||||
custom: {
|
||||
title: `text-[${PrimaryTextColor}] font-bold text-sm`,
|
||||
text: `text-[${HintTextColor}] text-sm`,
|
||||
error: `text-sm text-[${ErrorColor}]`,
|
||||
},
|
||||
};
|
||||
|
||||
newFieldKey = createRef<HTMLInputElement>();
|
||||
newFieldValue = createRef<HTMLTextAreaElement>();
|
||||
|
||||
onInput = (e: h.JSX.TargetedEvent<HTMLTextAreaElement, Event>, key?: string) => {
|
||||
onInput = (e: h.JSX.TargetedEvent<HTMLTextAreaElement | HTMLInputElement, Event>, key?: string) => {
|
||||
const lines = e.currentTarget.value.split("\n");
|
||||
e.currentTarget.setAttribute(
|
||||
"rows",
|
||||
@ -205,6 +170,9 @@ export class EditProfile extends Component<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Adding field ${this.newFieldKey.current.value}`);
|
||||
console.log(`Adding field ${this.newFieldValue.current.value}`);
|
||||
|
||||
this.setState({
|
||||
profileData: {
|
||||
...this.state.profileData,
|
||||
@ -217,7 +185,8 @@ export class EditProfile extends Component<Props, State> {
|
||||
this.newFieldValue.current.value = "";
|
||||
};
|
||||
|
||||
onSubmit = () => {
|
||||
onSubmit = (e: h.JSX.TargetedEvent) => {
|
||||
e.preventDefault();
|
||||
this.props.emit({
|
||||
type: "SaveProfile",
|
||||
ctx: this.props.ctx,
|
||||
|
29
app/UI/icons/plus-circle-icon.tsx
Normal file
29
app/UI/icons/plus-circle-icon.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
/** @jsx h */
|
||||
import { h } from "https://esm.sh/preact@10.17.1";
|
||||
|
||||
export function PlusCircleIcon(props: {
|
||||
class?: string | h.JSX.SignalLike<string | undefined> | undefined;
|
||||
style?:
|
||||
| string
|
||||
| h.JSX.CSSProperties
|
||||
| h.JSX.SignalLike<string | h.JSX.CSSProperties>
|
||||
| undefined;
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
class={props.class}
|
||||
style={props.style}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 8V16M8 12H16M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.67"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -13,17 +13,17 @@ export function UserIcon(props: {
|
||||
<svg
|
||||
class={props.class}
|
||||
style={props.style}
|
||||
viewBox="0 0 32 32"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="user-03">
|
||||
<path
|
||||
d="M4 26.6667C7.11438 23.3634 11.3427 21.3333 16 21.3333C20.6573 21.3333 24.8856 23.3634 28 26.6667M22 10C22 13.3137 19.3137 16 16 16C12.6863 16 10 13.3137 10 10C10 6.68629 12.6863 4 16 4C19.3137 4 22 6.68629 22 10Z"
|
||||
stroke-width="2.67"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M3 20C5.33579 17.5226 8.50702 16 12 16C15.493 16 18.6642 17.5226 21 20M16.5 7.5C16.5 9.98528 14.4853 12 12 12C9.51472 12 7.5 9.98528 7.5 7.5C7.5 5.01472 9.51472 3 12 3C14.4853 3 16.5 5.01472 16.5 7.5Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.67"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user