mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 15:43:20 +00:00
272 lines
8.5 KiB
TypeScript
272 lines
8.5 KiB
TypeScript
/** @jsx h */
|
|
import { createRef, Fragment, h } from "https://esm.sh/preact@10.17.1";
|
|
import { Avatar } from "./components/avatar.tsx";
|
|
import {
|
|
CenterClass,
|
|
DividerClass,
|
|
InputClass,
|
|
LinearGradientsClass,
|
|
NoOutlineClass,
|
|
} from "./components/tw.ts";
|
|
import { ProfileData } from "../features/profile.ts";
|
|
import {
|
|
DividerBackgroundColor,
|
|
ErrorColor,
|
|
HintLinkColor,
|
|
HintTextColor,
|
|
HoverButtonBackgroudColor,
|
|
PrimaryTextColor,
|
|
SecondaryBackgroundColor,
|
|
} from "./style/colors.ts";
|
|
import { Component, ComponentChildren } from "https://esm.sh/preact@10.11.3";
|
|
import { ProfileGetter } from "./search.tsx";
|
|
import { emitFunc } from "../event-bus.ts";
|
|
import { NostrAccountContext } from "../../libs/nostr.ts/nostr.ts";
|
|
|
|
export type SaveProfile = {
|
|
type: "SaveProfile";
|
|
profile: ProfileData;
|
|
ctx: NostrAccountContext;
|
|
};
|
|
|
|
type profileItem = {
|
|
key: string;
|
|
value?: string;
|
|
hint?: ComponentChildren;
|
|
};
|
|
|
|
type Props = {
|
|
ctx: NostrAccountContext;
|
|
profileGetter: ProfileGetter;
|
|
emit: emitFunc<SaveProfile>;
|
|
};
|
|
|
|
type State = {
|
|
profile: ProfileData | undefined;
|
|
newFieldKeyError: string;
|
|
};
|
|
|
|
export class EditProfile extends Component<Props, State> {
|
|
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-[${HoverButtonBackgroudColor}] ${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}]`,
|
|
},
|
|
};
|
|
|
|
componentDidMount() {
|
|
const { ctx, profileGetter } = this.props;
|
|
this.setState({
|
|
profile: profileGetter.getProfilesByPublicKey(ctx.publicKey)?.profile,
|
|
});
|
|
}
|
|
|
|
shouldComponentUpdate(_: Readonly<Props>, nextState: Readonly<State>, __: any): boolean {
|
|
return JSON.stringify(this.state.profile) != JSON.stringify(nextState.profile);
|
|
}
|
|
|
|
newFieldKey = createRef<HTMLInputElement>();
|
|
newFieldValue = createRef<HTMLTextAreaElement>();
|
|
|
|
onInput = (e: h.JSX.TargetedEvent<HTMLTextAreaElement, Event>, key?: string) => {
|
|
const lines = e.currentTarget.value.split("\n");
|
|
e.currentTarget.setAttribute(
|
|
"rows",
|
|
`${lines.length}`,
|
|
);
|
|
if (key) {
|
|
const value = e.currentTarget.value;
|
|
this.setState({
|
|
profile: {
|
|
...this.state.profile,
|
|
[key]: value,
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
addField = () => {
|
|
if (!this.newFieldKey.current || !this.newFieldValue.current) {
|
|
return;
|
|
}
|
|
|
|
if (this.newFieldKey.current.value.trim() == "") {
|
|
this.setState({
|
|
newFieldKeyError: "Key is required.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.setState({
|
|
profile: {
|
|
...this.state.profile,
|
|
[this.newFieldKey.current.value]: this.newFieldValue.current.value,
|
|
},
|
|
newFieldKeyError: "",
|
|
});
|
|
|
|
this.newFieldKey.current.value = "";
|
|
this.newFieldValue.current.value = "";
|
|
};
|
|
|
|
onSubmit = () => {
|
|
if (!this.state.profile) {
|
|
return;
|
|
}
|
|
|
|
this.props.emit({
|
|
type: "SaveProfile",
|
|
ctx: this.props.ctx,
|
|
profile: this.state.profile,
|
|
});
|
|
};
|
|
|
|
render() {
|
|
const profileItems: profileItem[] = [
|
|
{
|
|
key: "name",
|
|
value: this.state.profile?.name,
|
|
},
|
|
{
|
|
key: "banner",
|
|
value: this.state.profile?.banner,
|
|
},
|
|
{
|
|
key: "picture",
|
|
value: this.state.profile?.picture,
|
|
hint: (
|
|
<span class={this.styles.field.hint.text}>
|
|
You can upload your images on websites like{" "}
|
|
<a class={this.styles.field.hint.link} href="https://nostr.build/" target="_blank">
|
|
nostr.build
|
|
</a>
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
key: "about",
|
|
value: this.state.profile?.about,
|
|
},
|
|
{
|
|
key: "website",
|
|
value: this.state.profile?.website,
|
|
},
|
|
];
|
|
|
|
if (this.state.profile) {
|
|
for (const [key, value] of Object.entries(this.state.profile)) {
|
|
if (["name", "picture", "about", "website", "banner"].includes(key) || !value) {
|
|
continue;
|
|
}
|
|
|
|
profileItems.push({
|
|
key: key,
|
|
value: value,
|
|
});
|
|
}
|
|
}
|
|
|
|
const banner = this.state.profile?.banner
|
|
? (
|
|
<div
|
|
class={this.styles.banner.container}
|
|
style={{
|
|
background: `url(${
|
|
this.state.profile?.banner ? this.state.profile.banner : "default-bg.png"
|
|
}) no-repeat center center / cover`,
|
|
}}
|
|
>
|
|
<Avatar
|
|
picture={this.state.profile?.picture}
|
|
class={`w-24 h-24 m-auto absolute top-60 left-1/2 rounded-full box-border border-2 border-[${PrimaryTextColor}] -translate-x-2/4`}
|
|
/>
|
|
</div>
|
|
)
|
|
: (
|
|
<Avatar
|
|
picture={this.state.profile?.picture}
|
|
class={`w-24 h-24 m-auto box-border border-2 border-[${PrimaryTextColor}]`}
|
|
/>
|
|
);
|
|
|
|
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}
|
|
>
|
|
</textarea>
|
|
{item.hint}
|
|
</Fragment>
|
|
));
|
|
|
|
return (
|
|
<div class={this.styles.container}>
|
|
{banner}
|
|
{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>
|
|
);
|
|
}
|
|
}
|