Convert im_webapp from static mock prototypes to real backend integration

Implements all 11 product chapters (auth, messaging, contacts, groups,
files/favorites, calls, workplace/reports, settings, security) against the
real /v1/im REST API and native Tinode WS protocol, replacing the hardcoded
mock data the ba8f48ee prototype commit shipped with. Adds src/lib/im-api.js
as the REST client, plus supporting fixes across messages-view.jsx and the
attachment/upload/download pipeline (wrong SDK auth headers, group topic
naming, seq/timezone edge cases) found via live end-to-end testing against
the real backend.

Backup checkpoint before reconciling with origin/dev's parallel rewrite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-03 11:40:53 +08:00
co-authored by Claude Sonnet 5
parent ba8f48ee83
commit 251c9de87c
36 changed files with 52812 additions and 5199 deletions
+35 -26
View File
@@ -116,35 +116,44 @@ export default class CallIncoming extends React.Component {
}
render() {
const isAudioOnly = !!this.props.audioOnly;
const ringing = this.props.callState == CALL_STATE_INCOMING_RECEIVED;
const surfaceClass = isAudioOnly ? 'call-audio-surface' : 'call-video-surface';
const photoStyle = this.state.avatar ? {'--incoming-call-photo': `url(${this.state.avatar})`} : null;
return (
<div className="alert-container">
<div className="incoming-call">
<div className="caller-card incoming pulse">
<div className="avatar-box">
<LetterTile
authorizeURL={this.props.tinode.authorizeURL}
avatar={this.state.avatar || true}
topic={this.props.topic}
title={this.state.fullName} />
</div>
<div className="caller-name">{truncateString(this.state.fullName, MAX_PEER_TITLE_LENGTH)}
<BadgeList short={true} trustedBadges={this.state.trustedBadges} /></div>
</div>
<div id="controls">
{this.props.callState == CALL_STATE_INCOMING_RECEIVED ?
<>
<button className="danger" onClick={this.handleRejectCall}>
<i className="material-icons">call_end</i>
</button>
<button className="positive" onClick={this.handleAcceptCall}>
<i className="material-icons">call</i>
</button>
</>
:
null
}
<div className={`call-fullscreen incoming-call-overlay ${surfaceClass} incoming`}>
<div className="call-ambient" />
{this.state.avatar &&
<div className="incoming-call-photo-blur" style={photoStyle} />}
<div className="call-peer-card">
<div className="avatar-box call-product-avatar">
<LetterTile
authorizeURL={this.props.tinode.authorizeURL}
avatar={this.state.avatar || true}
topic={this.props.topic}
title={this.state.fullName} />
</div>
<h1>{truncateString(this.state.fullName, MAX_PEER_TITLE_LENGTH)}
<BadgeList short={true} trustedBadges={this.state.trustedBadges} /></h1>
<p>{isAudioOnly ? '对方邀请你语音通话...' : '对方邀请你视频通话...'}</p>
{ringing && <span className="call-ringing-dots"><i /><i /><i /></span>}
</div>
<footer className="call-incoming-actions">
{ringing ?
<>
<button type="button" className="call-control danger" onClick={this.handleRejectCall}>
<span><i className="material-icons">call_end</i></span>
<small>拒绝</small>
</button>
<button type="button" className="call-control" onClick={this.handleAcceptCall}>
<span><i className="material-icons">{isAudioOnly ? 'call' : 'videocam'}</i></span>
<small>接听</small>
</button>
</>
:
null
}
</footer>
</div>
);
}