feat(tauri-app): profile view, Avatar component, handle navigation
UI half of the profile feature. Mirrors the previous three commits
so the user can browse and edit profiles.
* `<Avatar did cid name size>` — reusable avatar component.
Falls back to an initial-letter (or "?" when name is empty) circle
when `cid` is null. Resolves the blob through the standard PDS
fetch path so it works for any author whose PDS the client can
reach.
* `<UserProfileView handle on_thread_click current_user_did>` —
public profile page. Fetches `GET /api/profile/<handle>` on
mount, renders the avatar / display name / bio / counts /
posts. The "edit profile" button is gated on
`current_user_did === profile.did` so a user browsing
someone else's profile can't issue an unintended `setMyProfile`
against their own DID.
* `PostCard` now renders an inline `<Avatar>` + clickable handle
button that calls a new `on_handle_click` prop. The clickable
area replaces the previous dead `<a href>` (Tauri webviews
have no router).
* `App.svelte` adds an `openUserProfile(handle)` handler that
sets `selectedHandle` + `view = "user"` and mounts
`<UserProfileView>`.
* New Tauri commands `profile_get_record` / `profile_set` in
`lib.rs` + matching client helpers `getMyProfile` /
`setMyProfile` in `client.ts`. The set command sends camelCase
field names; the PDS endpoint (previous commit) round-trips them
through `#[serde(rename_all = "camelCase")]`.
* Empty-state UX for users with no profile yet (new account, or a
third-party-PDS author whose profile the AppView hasn't indexed
yet): both the current-user "profile" view and the public
"user" view render a hint ("// no profile yet — click 'edit
profile' to set one up." / "// no profile yet.") instead of a
blank bio box.
* NavRail / NavRailHarness `View` union extended with "user"
so the navigation prop type accepts the new view.
This commit is contained in:
@@ -191,6 +191,9 @@ export type Post = {
|
||||
embed?: Embed | null;
|
||||
langs: string[];
|
||||
created_at: string;
|
||||
/// Resolved author-avatar CID from the AppView's `profiles`
|
||||
/// cache. NULL when the user has no profile record yet.
|
||||
avatar_cid?: string | null;
|
||||
};
|
||||
|
||||
export type TimelineResponse = {
|
||||
@@ -204,6 +207,11 @@ export type ProfileResponse = {
|
||||
posts: Post[];
|
||||
followers: number;
|
||||
following: number;
|
||||
display_name?: string | null;
|
||||
description?: string | null;
|
||||
avatar_cid?: string | null;
|
||||
banner_cid?: string | null;
|
||||
post_count: number;
|
||||
};
|
||||
|
||||
export type SearchResponse = {
|
||||
@@ -429,6 +437,33 @@ export async function listenTrayEvents(
|
||||
/// browser preview where no Tauri runtime is present, fall back
|
||||
/// to `window.open` and treat a popup-blocker denial as
|
||||
/// "fine, user can copy the URL themselves".
|
||||
export type ProfileRecord = {
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
avatar?: { ref: { $link: string }; mimeType?: string; size?: number };
|
||||
banner?: { ref: { $link: string }; mimeType?: string; size?: number };
|
||||
};
|
||||
|
||||
/// Read the authenticated user's `app.bsky.actor.profile` record.
|
||||
/// Returns `null` if no profile record exists yet (a brand-new
|
||||
/// account, or a user whose PDS hasn't pushed one).
|
||||
export async function getMyProfile(): Promise<ProfileRecord | null> {
|
||||
return await safeInvoke<ProfileRecord | null>("profile_get_record");
|
||||
}
|
||||
|
||||
/// Read-modify-write the authenticated user's profile. The Rust
|
||||
/// `profile_set` command fetches the existing record, overlays
|
||||
/// the supplied fields, and writes a new commit. `undefined` fields
|
||||
/// are preserved.
|
||||
export async function setMyProfile(fields: {
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
avatarBlobCid?: string;
|
||||
bannerBlobCid?: string;
|
||||
}): Promise<ProfileRecord | null> {
|
||||
return await safeInvoke<ProfileRecord | null>("profile_set", fields);
|
||||
}
|
||||
|
||||
export async function openExternalUrl(url: string): Promise<void> {
|
||||
try {
|
||||
if (isTauri()) {
|
||||
|
||||
Reference in New Issue
Block a user