blowater/app/UI/edit-profile.tsx

269 lines
8.6 KiB
TypeScript
Raw Normal View History

2023-06-30 14:05:57 +00:00
/** @jsx h */
2023-10-23 13:57:58 +00:00
import { createRef, Fragment, h } from "https://esm.sh/preact@10.17.1";
2023-06-30 14:05:57 +00:00
import { Avatar } from "./components/avatar.tsx";
import {
CenterClass,
DividerClass,
InputClass,
LinearGradientsClass,
NoOutlineClass,
} from "./components/tw.ts";
import { ProfileData } from "../features/profile.ts";
import {
DividerBackgroundColor,
2023-10-23 13:57:58 +00:00
ErrorColor,
2023-06-30 14:05:57 +00:00
HintLinkColor,
HintTextColor,
HoverButtonBackgroundColor,
2023-06-30 14:05:57 +00:00
PrimaryTextColor,
SecondaryBackgroundColor,
2023-06-30 14:05:57 +00:00
} from "./style/colors.ts";
2023-10-23 13:57:58 +00:00
import { Component, ComponentChildren } from "https://esm.sh/preact@10.11.3";
import { ProfileGetter } from "./search.tsx";
import { emitFunc } from "../event-bus.ts";
2024-01-01 17:28:10 +00:00
import { NostrAccountContext } from "../../libs/nostr.ts/nostr.ts";
import { robohash } from "./relay-detail.tsx";
2023-06-30 14:05:57 +00:00
2023-10-23 13:57:58 +00:00
export type SaveProfile = {
type: "SaveProfile";
2024-03-15 15:18:36 +00:00
profile: ProfileData | undefined;
2023-10-23 13:57:58 +00:00
ctx: NostrAccountContext;
2023-06-30 14:05:57 +00:00
};
2023-10-23 13:57:58 +00:00
type profileItem = {
2023-06-30 14:05:57 +00:00
key: string;
2023-10-23 13:57:58 +00:00
value?: string;
hint?: ComponentChildren;
2023-06-30 14:05:57 +00:00
};
2023-10-23 13:57:58 +00:00
type Props = {
ctx: NostrAccountContext;
profileGetter: ProfileGetter;
emit: emitFunc<SaveProfile>;
2023-06-30 14:05:57 +00:00
};
2023-10-23 13:57:58 +00:00
type State = {
profile: ProfileData | undefined;
newFieldKeyError: string;
2023-06-30 14:05:57 +00:00
};
2023-10-23 13:57:58 +00:00
export class EditProfile extends Component<Props, State> {
styles = {
2023-12-18 10:23:15 +00:00
container: `py-4 bg-[${SecondaryBackgroundColor}]`,
2023-10-23 13:57:58 +00:00
banner: {
2023-12-18 10:23:15 +00:00
container: `h-72 w-full rounded-lg mb-20 relative`,
2023-10-23 13:57:58 +00:00
},
field: {
2023-12-18 10:23:15 +00:00
title: `text-[${PrimaryTextColor}] mt-8`,
input: `${InputClass}`,
2023-10-23 13:57:58 +00:00
hint: {
2023-12-18 10:23:15 +00:00
text: `text-sm text-[${HintTextColor}]`,
link: `text-[${HintLinkColor}]`,
2023-10-23 13:57:58 +00:00
},
},
addButton:
`w-full mt-6 p-3 rounded-lg ${NoOutlineClass} text-[${PrimaryTextColor}] bg-[${DividerBackgroundColor}] hover:bg-[${HoverButtonBackgroundColor}] ${CenterClass}`,
2023-10-23 13:57:58 +00:00
submitButton:
2023-12-18 10:23:15 +00:00
`w-full p-3 rounded-lg ${NoOutlineClass} text-[${PrimaryTextColor}] ${CenterClass} ${LinearGradientsClass} hover:bg-gradient-to-l`,
divider: `${DividerClass}`,
2023-10-23 13:57:58 +00:00
custom: {
2023-12-18 10:23:15 +00:00
title: `text-[${PrimaryTextColor}] font-bold text-sm`,
text: `text-[${HintTextColor}] text-sm`,
error: `text-sm text-[${ErrorColor}]`,
2023-10-23 13:57:58 +00:00
},
};
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 = () => {
this.props.emit({
type: "SaveProfile",
ctx: this.props.ctx,
profile: this.state.profile,
});
2023-06-30 14:05:57 +00:00
};
2023-10-23 13:57:58 +00:00
render() {
const profileItems: profileItem[] = [
{
key: "name",
value: this.state.profile?.name,
},
{
key: "banner",
value: this.state.profile?.banner,
},
2023-10-23 13:57:58 +00:00
{
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`,
}}
>
2023-06-30 14:05:57 +00:00
<Avatar
picture={this.state.profile?.picture || robohash(this.props.ctx.publicKey.hex)}
2023-12-24 09:26:49 +00:00
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`}
2023-06-30 14:05:57 +00:00
/>
2023-10-23 13:57:58 +00:00
</div>
)
: (
<Avatar
picture={this.state.profile?.picture || robohash(this.props.ctx.publicKey.hex)}
2023-12-24 09:26:49 +00:00
class={`w-24 h-24 m-auto box-border border-2 border-[${PrimaryTextColor}]`}
2023-10-23 13:57:58 +00:00
/>
);
2023-06-30 14:05:57 +00:00
2023-10-23 13:57:58 +00:00
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}
2023-06-30 14:05:57 +00:00
>
2023-10-23 13:57:58 +00:00
</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>
2023-12-18 10:23:15 +00:00
<div class={`${DividerClass}`}></div>
2023-10-23 13:57:58 +00:00
<button class={this.styles.submitButton} onClick={this.onSubmit}>Update Profile</button>
2023-06-30 14:05:57 +00:00
</div>
2023-10-23 13:57:58 +00:00
);
}
2023-06-30 14:05:57 +00:00
}