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:
@@ -720,7 +720,65 @@ pub fn run() {
|
||||
pick_and_upload_image,
|
||||
show_notification,
|
||||
open_external_url,
|
||||
profile_get_record,
|
||||
profile_set,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running maarcadetweet");
|
||||
}
|
||||
#[tauri::command]
|
||||
async fn profile_get_record(
|
||||
state: tauri::State<'_, AppState>,
|
||||
) -> Result<Option<serde_json::Value>, String> {
|
||||
let sess = state
|
||||
.store
|
||||
.load()
|
||||
.ok_or_else(|| "not logged in".to_string())?;
|
||||
state
|
||||
.pds
|
||||
.get_profile_record(&sess.did, &sess.access_jwt)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn profile_set(
|
||||
state: tauri::State<'_, AppState>,
|
||||
fields: serde_json::Value,
|
||||
) -> Result<(), String> {
|
||||
let sess = state
|
||||
.store
|
||||
.load()
|
||||
.ok_or_else(|| "not logged in".to_string())?;
|
||||
let display_name = fields
|
||||
.get("displayName")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::to_string);
|
||||
let description = fields
|
||||
.get("description")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::to_string);
|
||||
let avatar_blob_cid = fields
|
||||
.get("avatarBlobCid")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::to_string);
|
||||
let banner_blob_cid = fields
|
||||
.get("bannerBlobCid")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::to_string);
|
||||
state
|
||||
.pds
|
||||
.set_profile(
|
||||
&sess.did,
|
||||
&serde_json::json!({
|
||||
"displayName": display_name,
|
||||
"description": description,
|
||||
"avatarBlobCid": avatar_blob_cid,
|
||||
"bannerBlobCid": banner_blob_cid,
|
||||
}),
|
||||
&sess.access_jwt,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user