From 4b016c908dda2019f3bf89e5a3d2eae535e5fbd2 Mon Sep 17 00:00:00 2001 From: polwex Date: Thu, 18 Sep 2025 00:24:39 +0700 Subject: oioi --- front/src/Router.tsx | 8 + front/src/components/Avatar.tsx | 26 ++- front/src/components/Icon.tsx | 10 +- front/src/components/ProfileEditor.tsx | 280 ------------------------- front/src/components/modals/UserModal.tsx | 65 ++++++ front/src/components/post/Post.tsx | 1 + front/src/components/profile/Editor.tsx | 262 ++++++++++++++++++++++++ front/src/components/profile/Profile.tsx | 67 ++++++ front/src/logic/api.ts | 2 +- front/src/logic/nostrill.ts | 21 ++ front/src/logic/requests/nostrill.ts | 16 +- front/src/pages/Feed.tsx | 10 +- front/src/pages/User.tsx | 85 ++++++-- front/src/state/state.ts | 14 +- front/src/styles/Profile.css | 325 ++++++++++++++++++++++++++++++ front/src/styles/ProfileEditor.css | 325 ------------------------------ front/src/styles/feed.css | 7 +- front/src/types/nostrill.ts | 1 + 18 files changed, 861 insertions(+), 664 deletions(-) delete mode 100644 front/src/components/ProfileEditor.tsx create mode 100644 front/src/components/modals/UserModal.tsx create mode 100644 front/src/components/profile/Editor.tsx create mode 100644 front/src/components/profile/Profile.tsx create mode 100644 front/src/styles/Profile.css delete mode 100644 front/src/styles/ProfileEditor.css (limited to 'front') diff --git a/front/src/Router.tsx b/front/src/Router.tsx index 83d212f..1293709 100644 --- a/front/src/Router.tsx +++ b/front/src/Router.tsx @@ -27,3 +27,11 @@ function toGlobal() { export function P404() { return

404

; } +export function ErrorPage({ msg }: { msg: string }) { + return ( +
+ +

{msg}

+
+ ); +} diff --git a/front/src/components/Avatar.tsx b/front/src/components/Avatar.tsx index 0f3dc90..a071655 100644 --- a/front/src/components/Avatar.tsx +++ b/front/src/components/Avatar.tsx @@ -1,20 +1,21 @@ import useLocalState from "@/state/state"; -import type { Ship } from "@/types/urbit"; import Sigil from "./Sigil"; -import ShipModal from "./modals/ShipModal"; import { isValidPatp } from "urbit-ob"; -import type { UserProfile } from "@/types/nostrill"; +import type { UserProfile, UserType } from "@/types/nostrill"; import Icon from "@/components/Icon"; +import UserModal from "./modals/UserModal"; export default function ({ - p, + user, + userString, size, color, noClickOnName, profile, picOnly = false, }: { - p: Ship; + user: UserType; + userString: string; size: number; color?: string; noClickOnName?: boolean; @@ -23,10 +24,11 @@ export default function ({ }) { const { setModal } = useLocalState((s) => ({ setModal: s.setModal })); // TODO revisit this when %whom updates + console.log({ profile }); const avatarInner = profile ? ( - - ) : isValidPatp(p) ? ( - + + ) : "urbit" in user && isValidPatp(user.urbit) ? ( + ) : ( ); @@ -41,14 +43,18 @@ export default function ({ function openModal(e: React.MouseEvent) { if (noClickOnName) return; e.stopPropagation(); - setModal(); + setModal(); } const name = (
{profile ? (

{profile.name}

+ ) : "urbit" in user ? ( +

+ {user.urbit.length > 28 ? "Anon" : user.urbit} +

) : ( -

{p.length > 28 ? "Anon" : p}

+

{user.nostr}

)}
); diff --git a/front/src/components/Icon.tsx b/front/src/components/Icon.tsx index a316e08..797a87b 100644 --- a/front/src/components/Icon.tsx +++ b/front/src/components/Icon.tsx @@ -65,7 +65,7 @@ interface IconProps { size?: number; className?: string; title?: string; - onClick?: (e?: React.MouseEvent) => void; + onClick?: (e: React.MouseEvent) => any; color?: "primary" | "text" | "textSecondary" | "textMuted" | "custom"; customColor?: string; } @@ -84,7 +84,11 @@ const Icon: React.FC = ({ // Simple filter based on theme - icons should match text const getFilter = () => { // For dark themes, invert the black SVGs to white - if (theme.name === "dark" || theme.name === "noir" || theme.name === "gruvbox") { + if ( + theme.name === "dark" || + theme.name === "noir" || + theme.name === "gruvbox" + ) { return "invert(1)"; } // For light themes with dark text, keep as is @@ -130,4 +134,4 @@ const Icon: React.FC = ({ ); }; -export default Icon; \ No newline at end of file +export default Icon; diff --git a/front/src/components/ProfileEditor.tsx b/front/src/components/ProfileEditor.tsx deleted file mode 100644 index 9a7493f..0000000 --- a/front/src/components/ProfileEditor.tsx +++ /dev/null @@ -1,280 +0,0 @@ -import { useState, useEffect } from "react"; -import type { UserProfile } from "@/types/nostrill"; -import useLocalState from "@/state/state"; -import Icon from "@/components/Icon"; -import toast from "react-hot-toast"; -import Avatar from "./Avatar"; - -interface ProfileEditorProps { - ship: string; - onSave?: () => void; -} - -const ProfileEditor: React.FC = ({ ship, onSave }) => { - const { api, profiles } = useLocalState((s) => ({ - api: s.api, - profiles: s.profiles, - })); - const isOwnProfile = ship === api?.airlock.our; - - // Initialize state with existing profile or defaults - const existingProfile = profiles.get(ship); - const [name, setName] = useState(existingProfile?.name || ""); - const [picture, setPicture] = useState(existingProfile?.picture || ""); - const [about, setAbout] = useState(existingProfile?.about || ""); - const [customFields, setCustomFields] = useState< - Array<{ key: string; value: string }> - >( - Object.entries(existingProfile?.other || {}).map(([key, value]) => ({ - key, - value, - })), - ); - const [isEditing, setIsEditing] = useState(false); - const [isSaving, setIsSaving] = useState(false); - - useEffect(() => { - const profile = profiles.get(ship); - if (profile) { - setName(profile.name || ""); - setPicture(profile.picture || ""); - setAbout(profile.about || ""); - setCustomFields( - Object.entries(profile.other || {}).map(([key, value]) => ({ - key, - value, - })), - ); - } - }, [ship, profiles]); - - const handleAddCustomField = () => { - setCustomFields([...customFields, { key: "", value: "" }]); - }; - - const handleUpdateCustomField = ( - index: number, - field: "key" | "value", - newValue: string, - ) => { - const updated = [...customFields]; - updated[index][field] = newValue; - setCustomFields(updated); - }; - - const handleRemoveCustomField = (index: number) => { - setCustomFields(customFields.filter((_, i) => i !== index)); - }; - - const handleSave = async () => { - setIsSaving(true); - try { - // Convert custom fields array to object - const other: Record = {}; - customFields.forEach(({ key, value }) => { - if (key.trim()) { - other[key.trim()] = value; - } - }); - - const profile: UserProfile = { - name, - picture, - about, - other, - }; - - // Call API to save profile - if (api && typeof api.createProfile === "function") { - await api.createProfile(profile); - } else { - throw new Error("Profile update API not available"); - } - - toast.success("Profile updated successfully"); - setIsEditing(false); - onSave?.(); - } catch (error) { - toast.error("Failed to update profile"); - console.error("Failed to save profile:", error); - } finally { - setIsSaving(false); - } - }; - - const handleCancel = () => { - // Reset to original values - const profile = profiles.get(ship); - if (profile) { - setName(profile.name || ""); - setPicture(profile.picture || ""); - setAbout(profile.about || ""); - setCustomFields( - Object.entries(profile.other || {}).map(([key, value]) => ({ - key, - value, - })), - ); - } - setIsEditing(false); - }; - - if (!isOwnProfile) { - // View-only mode for other users' profiles - no editing allowed - return ( -
-
- -
-
-

{name || ship}

- {about &&

{about}

} - - {customFields.length > 0 && ( -
-

Additional Info

- {customFields.map(({ key, value }, index) => ( -
- {key}: - {value} -
- ))} -
- )} -
-
- ); - } - - return ( -
-
-

Edit Profile

- {isOwnProfile && !isEditing && ( - - )} -
- - {isEditing ? ( -
-
- - setName(e.target.value)} - placeholder="Your display name" - /> -
- -
- - setPicture(e.target.value)} - placeholder="https://example.com/avatar.jpg" - /> -
- -
-
- -
- -