优化
This commit is contained in:
181
client/flutter/lib/models/group.dart
Normal file
181
client/flutter/lib/models/group.dart
Normal file
@@ -0,0 +1,181 @@
|
||||
class Group {
|
||||
final String id;
|
||||
final String name;
|
||||
final String? avatar;
|
||||
final String? description;
|
||||
final String? owner;
|
||||
final List<GroupMember> members;
|
||||
final List<GroupPanel> panels;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? updatedAt;
|
||||
|
||||
Group({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.avatar,
|
||||
this.description,
|
||||
this.owner,
|
||||
this.members = const [],
|
||||
this.panels = const [],
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
factory Group.fromJson(Map<String, dynamic> json) {
|
||||
return Group(
|
||||
id: (json['_id'] ?? json['id'] ?? '').toString(),
|
||||
name: json['name'] ?? '',
|
||||
avatar: json['avatar'],
|
||||
description: json['description'],
|
||||
owner: json['owner']?.toString(),
|
||||
members: (json['members'] as List?)
|
||||
?.map((e) => GroupMember.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
panels: (json['panels'] as List?)
|
||||
?.map((e) => GroupPanel.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.parse(json['createdAt'].toString())
|
||||
: null,
|
||||
updatedAt: json['updatedAt'] != null
|
||||
? DateTime.parse(json['updatedAt'].toString())
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'_id': id,
|
||||
'name': name,
|
||||
'avatar': avatar,
|
||||
'description': description,
|
||||
'owner': owner,
|
||||
'members': members.map((e) => e.toJson()).toList(),
|
||||
'panels': panels.map((e) => e.toJson()).toList(),
|
||||
'createdAt': createdAt?.toIso8601String(),
|
||||
'updatedAt': updatedAt?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
/// 群组成员数量
|
||||
int get memberCount => members.length;
|
||||
|
||||
/// 获取第一个文本面板(频道)用于聊天
|
||||
GroupPanel? get firstTextPanel {
|
||||
try {
|
||||
return panels.firstWhere((p) => p.type == 0);
|
||||
} catch (_) {
|
||||
return panels.isNotEmpty ? panels.first : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GroupMember {
|
||||
final String? userId;
|
||||
final List<String>? roles;
|
||||
final DateTime? muteUntil;
|
||||
|
||||
GroupMember({
|
||||
this.userId,
|
||||
this.roles,
|
||||
this.muteUntil,
|
||||
});
|
||||
|
||||
factory GroupMember.fromJson(Map<String, dynamic> json) {
|
||||
return GroupMember(
|
||||
userId: json['userId']?.toString(),
|
||||
roles: (json['roles'] as List?)?.map((e) => e.toString()).toList(),
|
||||
muteUntil: json['muteUntil'] != null
|
||||
? DateTime.parse(json['muteUntil'].toString())
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'userId': userId,
|
||||
'roles': roles,
|
||||
'muteUntil': muteUntil?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 群组面板类型:
|
||||
/// 0 = 文本频道
|
||||
/// 1 = 面板组(文件夹)
|
||||
/// 2 = 插件面板
|
||||
class GroupPanel {
|
||||
final String id;
|
||||
final String name;
|
||||
final String? parentId;
|
||||
final int type;
|
||||
final String? provider;
|
||||
final String? pluginPanelName;
|
||||
final Map<String, dynamic>? meta;
|
||||
|
||||
GroupPanel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.parentId,
|
||||
this.type = 0,
|
||||
this.provider,
|
||||
this.pluginPanelName,
|
||||
this.meta,
|
||||
});
|
||||
|
||||
factory GroupPanel.fromJson(Map<String, dynamic> json) {
|
||||
return GroupPanel(
|
||||
id: (json['id'] ?? '').toString(),
|
||||
name: json['name'] ?? '',
|
||||
parentId: json['parentId']?.toString(),
|
||||
type: json['type'] ?? 0,
|
||||
provider: json['provider'],
|
||||
pluginPanelName: json['pluginPanelName'],
|
||||
meta: json['meta'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'parentId': parentId,
|
||||
'type': type,
|
||||
'provider': provider,
|
||||
'pluginPanelName': pluginPanelName,
|
||||
'meta': meta,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// getGroupBasicInfo 返回的基本群组信息
|
||||
class GroupBasicInfo {
|
||||
final String groupId;
|
||||
final String name;
|
||||
final String? avatar;
|
||||
final String? description;
|
||||
final int memberCount;
|
||||
final String? backgroundImage;
|
||||
|
||||
GroupBasicInfo({
|
||||
required this.groupId,
|
||||
required this.name,
|
||||
this.avatar,
|
||||
this.description,
|
||||
this.memberCount = 0,
|
||||
this.backgroundImage,
|
||||
});
|
||||
|
||||
factory GroupBasicInfo.fromJson(Map<String, dynamic> json) {
|
||||
return GroupBasicInfo(
|
||||
groupId: (json['groupId'] ?? '').toString(),
|
||||
name: json['name'] ?? '',
|
||||
avatar: json['avatar'],
|
||||
description: json['description'],
|
||||
memberCount: json['memberCount'] ?? 0,
|
||||
backgroundImage: json['backgroundImage'],
|
||||
);
|
||||
}
|
||||
}
|
||||
166
client/flutter/lib/models/invite.dart
Normal file
166
client/flutter/lib/models/invite.dart
Normal file
@@ -0,0 +1,166 @@
|
||||
class Invite {
|
||||
final String id;
|
||||
final String code;
|
||||
final String salesId;
|
||||
final String groupId;
|
||||
final String? link;
|
||||
final String? qrCodeUrl;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? expiresAt;
|
||||
final int clickCount;
|
||||
final int scanCount;
|
||||
final int joinCount;
|
||||
final String status;
|
||||
|
||||
Invite({
|
||||
required this.id,
|
||||
required this.code,
|
||||
required this.salesId,
|
||||
required this.groupId,
|
||||
this.link,
|
||||
this.qrCodeUrl,
|
||||
this.createdAt,
|
||||
this.expiresAt,
|
||||
this.clickCount = 0,
|
||||
this.scanCount = 0,
|
||||
this.joinCount = 0,
|
||||
this.status = 'active',
|
||||
});
|
||||
|
||||
factory Invite.fromJson(Map<String, dynamic> json) {
|
||||
return Invite(
|
||||
id: (json['_id'] ?? json['id'] ?? '').toString(),
|
||||
code: json['code'] ?? '',
|
||||
salesId: (json['salesId'] ?? '').toString(),
|
||||
groupId: (json['groupId'] ?? '').toString(),
|
||||
link: json['link'],
|
||||
qrCodeUrl: json['qrCodeUrl'],
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.parse(json['createdAt'].toString())
|
||||
: null,
|
||||
expiresAt: json['expiresAt'] != null
|
||||
? DateTime.parse(json['expiresAt'].toString())
|
||||
: null,
|
||||
clickCount: json['clickCount'] ?? 0,
|
||||
scanCount: json['scanCount'] ?? 0,
|
||||
joinCount: json['joinCount'] ?? 0,
|
||||
status: json['status'] ?? 'active',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'_id': id,
|
||||
'code': code,
|
||||
'salesId': salesId,
|
||||
'groupId': groupId,
|
||||
'link': link,
|
||||
'qrCodeUrl': qrCodeUrl,
|
||||
'createdAt': createdAt?.toIso8601String(),
|
||||
'expiresAt': expiresAt?.toIso8601String(),
|
||||
'clickCount': clickCount,
|
||||
'scanCount': scanCount,
|
||||
'joinCount': joinCount,
|
||||
'status': status,
|
||||
};
|
||||
}
|
||||
|
||||
bool get isExpired =>
|
||||
expiresAt != null && DateTime.now().isAfter(expiresAt!);
|
||||
bool get isActive => status == 'active' && !isExpired;
|
||||
bool get isValid => isActive;
|
||||
}
|
||||
|
||||
/// 单个邀请的统计数据,由 invite.getStats 返回
|
||||
class InviteStats {
|
||||
final String code;
|
||||
final String? link;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? expiresAt;
|
||||
final InviteStatsDetail stats;
|
||||
final List<AccessLog> recentAccess;
|
||||
|
||||
InviteStats({
|
||||
required this.code,
|
||||
this.link,
|
||||
this.createdAt,
|
||||
this.expiresAt,
|
||||
required this.stats,
|
||||
this.recentAccess = const [],
|
||||
});
|
||||
|
||||
factory InviteStats.fromJson(Map<String, dynamic> json) {
|
||||
return InviteStats(
|
||||
code: json['code'] ?? '',
|
||||
link: json['link'],
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.parse(json['createdAt'].toString())
|
||||
: null,
|
||||
expiresAt: json['expiresAt'] != null
|
||||
? DateTime.parse(json['expiresAt'].toString())
|
||||
: null,
|
||||
stats: InviteStatsDetail.fromJson(json['stats'] ?? {}),
|
||||
recentAccess: (json['recentAccess'] as List?)
|
||||
?.map((e) => AccessLog.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InviteStatsDetail {
|
||||
final int clicks;
|
||||
final int scans;
|
||||
final int joins;
|
||||
final String conversionRate;
|
||||
|
||||
InviteStatsDetail({
|
||||
this.clicks = 0,
|
||||
this.scans = 0,
|
||||
this.joins = 0,
|
||||
this.conversionRate = '0',
|
||||
});
|
||||
|
||||
factory InviteStatsDetail.fromJson(Map<String, dynamic> json) {
|
||||
return InviteStatsDetail(
|
||||
clicks: json['clicks'] ?? 0,
|
||||
scans: json['scans'] ?? 0,
|
||||
joins: json['joins'] ?? 0,
|
||||
conversionRate: json['conversionRate']?.toString() ?? '0',
|
||||
);
|
||||
}
|
||||
|
||||
double get conversionRateDouble => double.tryParse(conversionRate) ?? 0.0;
|
||||
}
|
||||
|
||||
/// 来自后端的访问日志条目
|
||||
class AccessLog {
|
||||
final String? id;
|
||||
final String inviteCode;
|
||||
final String? visitorId;
|
||||
final String accessType;
|
||||
final DateTime? timestamp;
|
||||
final String? ipAddress;
|
||||
|
||||
AccessLog({
|
||||
this.id,
|
||||
required this.inviteCode,
|
||||
this.visitorId,
|
||||
required this.accessType,
|
||||
this.timestamp,
|
||||
this.ipAddress,
|
||||
});
|
||||
|
||||
factory AccessLog.fromJson(Map<String, dynamic> json) {
|
||||
return AccessLog(
|
||||
id: (json['_id'] ?? json['id'])?.toString(),
|
||||
inviteCode: json['inviteCode'] ?? '',
|
||||
visitorId: json['visitorId']?.toString(),
|
||||
accessType: json['accessType'] ?? '',
|
||||
timestamp: json['timestamp'] != null
|
||||
? DateTime.parse(json['timestamp'].toString())
|
||||
: null,
|
||||
ipAddress: json['ipAddress'],
|
||||
);
|
||||
}
|
||||
}
|
||||
81
client/flutter/lib/models/message.dart
Normal file
81
client/flutter/lib/models/message.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
class Message {
|
||||
final String id;
|
||||
final String content;
|
||||
final String? author;
|
||||
final String? groupId;
|
||||
final String converseId;
|
||||
final bool hasRecall;
|
||||
final Map<String, dynamic>? meta;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? updatedAt;
|
||||
|
||||
Message({
|
||||
required this.id,
|
||||
required this.content,
|
||||
this.author,
|
||||
this.groupId,
|
||||
required this.converseId,
|
||||
this.hasRecall = false,
|
||||
this.meta,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
factory Message.fromJson(Map<String, dynamic> json) {
|
||||
return Message(
|
||||
id: (json['_id'] ?? json['id'] ?? '').toString(),
|
||||
content: json['content'] ?? '',
|
||||
author: json['author']?.toString(),
|
||||
groupId: json['groupId']?.toString(),
|
||||
converseId: (json['converseId'] ?? '').toString(),
|
||||
hasRecall: json['hasRecall'] ?? false,
|
||||
meta: json['meta'],
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.parse(json['createdAt'].toString())
|
||||
: null,
|
||||
updatedAt: json['updatedAt'] != null
|
||||
? DateTime.parse(json['updatedAt'].toString())
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'_id': id,
|
||||
'content': content,
|
||||
'author': author,
|
||||
'groupId': groupId,
|
||||
'converseId': converseId,
|
||||
'hasRecall': hasRecall,
|
||||
'meta': meta,
|
||||
'createdAt': createdAt?.toIso8601String(),
|
||||
'updatedAt': updatedAt?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Determine message type from meta
|
||||
MessageType get type {
|
||||
if (hasRecall) return MessageType.recalled;
|
||||
final metaType = meta?['type'];
|
||||
if (metaType == 'image' || metaType == 'file') {
|
||||
return MessageType.file;
|
||||
}
|
||||
if (metaType == 'system') {
|
||||
return MessageType.system;
|
||||
}
|
||||
return MessageType.text;
|
||||
}
|
||||
|
||||
/// Convenience alias for author (sender ID)
|
||||
String get senderId => author ?? '';
|
||||
|
||||
/// For display purposes - get sender name from meta if available
|
||||
String get senderName => meta?['nickname']?.toString() ?? '';
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
text,
|
||||
file,
|
||||
system,
|
||||
recalled,
|
||||
}
|
||||
5
client/flutter/lib/models/models.dart
Normal file
5
client/flutter/lib/models/models.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
export 'user.dart';
|
||||
export 'message.dart';
|
||||
export 'group.dart';
|
||||
export 'invite.dart';
|
||||
export 'stats.dart';
|
||||
154
client/flutter/lib/models/stats.dart
Normal file
154
client/flutter/lib/models/stats.dart
Normal file
@@ -0,0 +1,154 @@
|
||||
/// My stats returned by stats.getMyStats
|
||||
class MyStats {
|
||||
final TotalStats total;
|
||||
final TodayStats today;
|
||||
final List<InviteSummary> recentInvites;
|
||||
|
||||
MyStats({
|
||||
required this.total,
|
||||
required this.today,
|
||||
this.recentInvites = const [],
|
||||
});
|
||||
|
||||
factory MyStats.fromJson(Map<String, dynamic> json) {
|
||||
return MyStats(
|
||||
total: TotalStats.fromJson(json['total'] ?? {}),
|
||||
today: TodayStats.fromJson(json['today'] ?? {}),
|
||||
recentInvites: (json['recentInvites'] as List?)
|
||||
?.map((e) => InviteSummary.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TotalStats {
|
||||
final int totalInvites;
|
||||
final int totalJoins;
|
||||
final int totalConversions;
|
||||
final double totalRevenue;
|
||||
|
||||
TotalStats({
|
||||
this.totalInvites = 0,
|
||||
this.totalJoins = 0,
|
||||
this.totalConversions = 0,
|
||||
this.totalRevenue = 0,
|
||||
});
|
||||
|
||||
factory TotalStats.fromJson(Map<String, dynamic> json) {
|
||||
return TotalStats(
|
||||
totalInvites: json['totalInvites'] ?? 0,
|
||||
totalJoins: json['totalJoins'] ?? 0,
|
||||
totalConversions: json['totalConversions'] ?? 0,
|
||||
totalRevenue: (json['totalRevenue'] ?? 0).toDouble(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TodayStats {
|
||||
final int invitesCreated;
|
||||
final int joins;
|
||||
final int conversions;
|
||||
final double revenue;
|
||||
|
||||
TodayStats({
|
||||
this.invitesCreated = 0,
|
||||
this.joins = 0,
|
||||
this.conversions = 0,
|
||||
this.revenue = 0,
|
||||
});
|
||||
|
||||
factory TodayStats.fromJson(Map<String, dynamic> json) {
|
||||
return TodayStats(
|
||||
invitesCreated: json['invitesCreated'] ?? 0,
|
||||
joins: json['joins'] ?? 0,
|
||||
conversions: json['conversions'] ?? 0,
|
||||
revenue: (json['revenue'] ?? 0).toDouble(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InviteSummary {
|
||||
final String? id;
|
||||
final String? code;
|
||||
final String? groupId;
|
||||
final String? link;
|
||||
final int? clickCount;
|
||||
final int? joinCount;
|
||||
final DateTime? createdAt;
|
||||
|
||||
InviteSummary({
|
||||
this.id,
|
||||
this.code,
|
||||
this.groupId,
|
||||
this.link,
|
||||
this.clickCount,
|
||||
this.joinCount,
|
||||
this.createdAt,
|
||||
});
|
||||
|
||||
factory InviteSummary.fromJson(Map<String, dynamic> json) {
|
||||
return InviteSummary(
|
||||
id: (json['_id'] ?? json['id'])?.toString(),
|
||||
code: json['code'],
|
||||
groupId: json['groupId']?.toString(),
|
||||
link: json['link'],
|
||||
clickCount: json['clickCount'],
|
||||
joinCount: json['joinCount'],
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.parse(json['createdAt'].toString())
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ranking entry returned by stats.getRanking
|
||||
class RankingEntry {
|
||||
final int rank;
|
||||
final String salesId;
|
||||
final int totalJoins;
|
||||
final int totalConversions;
|
||||
final double totalRevenue;
|
||||
final Map<String, dynamic>? user;
|
||||
|
||||
RankingEntry({
|
||||
this.rank = 0,
|
||||
required this.salesId,
|
||||
this.totalJoins = 0,
|
||||
this.totalConversions = 0,
|
||||
this.totalRevenue = 0,
|
||||
this.user,
|
||||
});
|
||||
|
||||
factory RankingEntry.fromJson(Map<String, dynamic> json) {
|
||||
return RankingEntry(
|
||||
rank: json['rank'] ?? 0,
|
||||
salesId: (json['_id'] ?? json['salesId'] ?? '').toString(),
|
||||
totalJoins: json['totalJoins'] ?? 0,
|
||||
totalConversions: json['totalConversions'] ?? 0,
|
||||
totalRevenue: (json['totalRevenue'] ?? 0).toDouble(),
|
||||
user: json['user'],
|
||||
);
|
||||
}
|
||||
|
||||
String get displayName =>
|
||||
user?['nickname']?.toString() ?? user?['email']?.toString() ?? salesId;
|
||||
}
|
||||
|
||||
/// Platform stats returned by stats.getPlatformStats
|
||||
class PlatformStats {
|
||||
final Map<String, dynamic> platform;
|
||||
final Map<String, dynamic> today;
|
||||
|
||||
PlatformStats({
|
||||
this.platform = const {},
|
||||
this.today = const {},
|
||||
});
|
||||
|
||||
factory PlatformStats.fromJson(Map<String, dynamic> json) {
|
||||
return PlatformStats(
|
||||
platform: json['platform'] ?? {},
|
||||
today: json['today'] ?? {},
|
||||
);
|
||||
}
|
||||
}
|
||||
118
client/flutter/lib/models/user.dart
Normal file
118
client/flutter/lib/models/user.dart
Normal file
@@ -0,0 +1,118 @@
|
||||
class User {
|
||||
final String id;
|
||||
final String? email;
|
||||
final String? username;
|
||||
final String? nickname;
|
||||
final String? avatar;
|
||||
final String? discriminator;
|
||||
final bool? temporary;
|
||||
final String? type;
|
||||
final bool? emailVerified;
|
||||
final bool? banned;
|
||||
final Map<String, dynamic>? extra;
|
||||
final DateTime? createdAt;
|
||||
|
||||
User({
|
||||
required this.id,
|
||||
this.email,
|
||||
this.username,
|
||||
this.nickname,
|
||||
this.avatar,
|
||||
this.discriminator,
|
||||
this.temporary,
|
||||
this.type,
|
||||
this.emailVerified,
|
||||
this.banned,
|
||||
this.extra,
|
||||
this.createdAt,
|
||||
});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
return User(
|
||||
id: (json['_id'] ?? json['id'] ?? '').toString(),
|
||||
email: json['email'],
|
||||
username: json['username'],
|
||||
nickname: json['nickname'],
|
||||
avatar: json['avatar'],
|
||||
discriminator: json['discriminator']?.toString(),
|
||||
temporary: json['temporary'],
|
||||
type: json['type'],
|
||||
emailVerified: json['emailVerified'],
|
||||
banned: json['banned'],
|
||||
extra: json['extra'],
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.parse(json['createdAt'].toString())
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'_id': id,
|
||||
'email': email,
|
||||
'username': username,
|
||||
'nickname': nickname,
|
||||
'avatar': avatar,
|
||||
'discriminator': discriminator,
|
||||
'temporary': temporary,
|
||||
'type': type,
|
||||
'emailVerified': emailVerified,
|
||||
'banned': banned,
|
||||
'extra': extra,
|
||||
'createdAt': createdAt?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Display name: nickname first, then username, then email prefix
|
||||
String get displayName {
|
||||
if (nickname != null && nickname!.isNotEmpty) return nickname!;
|
||||
if (username != null && username!.isNotEmpty) return username!;
|
||||
if (email != null && email!.isNotEmpty) return email!.split('@').first;
|
||||
return 'User';
|
||||
}
|
||||
|
||||
/// Check if user is a guest/temporary user
|
||||
bool get isGuest => temporary == true;
|
||||
|
||||
/// Role checks - these may come from extra fields or custom logic
|
||||
/// In Tailchat, roles are not built-in; they are managed by the plugin
|
||||
bool get isAdmin {
|
||||
final role = extra?['role'];
|
||||
return role == 'admin' || role == 'super_admin';
|
||||
}
|
||||
|
||||
bool get isSuperAdmin {
|
||||
final role = extra?['role'];
|
||||
return role == 'super_admin';
|
||||
}
|
||||
|
||||
bool get isSales {
|
||||
final role = extra?['role'];
|
||||
return role == 'sales' || role == null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Tailchat login/register response wraps the user info with a token.
|
||||
/// The backend returns the user object directly with a `token` field appended.
|
||||
/// The gateway wraps all responses as `{ code, data }`.
|
||||
class AuthResponse {
|
||||
final User user;
|
||||
final String token;
|
||||
|
||||
AuthResponse({required this.user, required this.token});
|
||||
|
||||
/// Parse from the gateway response.
|
||||
/// Gateway returns: { "code": 200, "data": { ...userFields..., "token": "jwt..." } }
|
||||
factory AuthResponse.fromJson(Map<String, dynamic> json) {
|
||||
// The gateway wraps response in { code, data }
|
||||
final data = json['data'] ?? json;
|
||||
|
||||
final token = data['token'] ?? '';
|
||||
final user = User.fromJson(data);
|
||||
|
||||
return AuthResponse(
|
||||
user: user,
|
||||
token: token,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user