feat: display joined at and user avatar in members page (#7809)

* feat: new settings page design

* feat: support display member join at and avatar

* chore: bump version 0.9.0

* chore: update translations
This commit is contained in:
Lucas 2025-04-23 13:41:52 +08:00 committed by GitHub
parent 4626c18ced
commit 1977cf6637
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 182 additions and 92 deletions

View File

@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
APPFLOWY_VERSION = "0.8.9"
APPFLOWY_VERSION = "0.9.0"
FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"

View File

@ -27,6 +27,7 @@ import 'package:appflowy/workspace/presentation/settings/widgets/web_url_hint_wi
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@ -60,6 +61,7 @@ class SettingsDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width * 0.6;
final theme = AppFlowyTheme.of(context);
return BlocProvider<SettingsDialogBloc>(
create: (context) => SettingsDialogBloc(
user,
@ -72,12 +74,12 @@ class SettingsDialog extends StatelessWidget {
constraints: const BoxConstraints(minWidth: 564),
child: ScaffoldMessenger(
child: Scaffold(
backgroundColor: Colors.transparent,
backgroundColor: theme.backgroundColorScheme.primary,
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 200,
width: 204,
child: SettingsMenu(
userProfile: user,
changeSelectedPage: (index) => context
@ -88,6 +90,10 @@ class SettingsDialog extends StatelessWidget {
isBillingEnabled: state.isBillingEnabled,
),
),
AFDivider(
axis: Axis.vertical,
color: theme.borderColorScheme.primary,
),
Expanded(
child: getSettingsView(
context

View File

@ -88,17 +88,18 @@ class _Description extends StatelessWidget {
await showConfirmDialog(
context: context,
style: ConfirmPopupStyle.cancelAndOk,
title: 'Reset the invite link?',
description:
'Resetting will deactivate the current link for all space members and generate a new one. The old link will no longer be available.',
confirmLabel: 'Reset',
title: LocaleKeys.settings_appearance_members_resetInviteLink.tr(),
description: LocaleKeys
.settings_appearance_members_resetInviteLinkDescription
.tr(),
confirmLabel: LocaleKeys.settings_appearance_members_reset.tr(),
onConfirm: () {
context.read<WorkspaceMemberBloc>().add(
const WorkspaceMemberEvent.generateInviteLink(),
);
},
confirmButtonBuilder: (_) => AFFilledTextButton.destructive(
text: 'Reset',
text: LocaleKeys.settings_appearance_members_reset.tr(),
onTap: () {
context.read<WorkspaceMemberBloc>().add(
const WorkspaceMemberEvent.generateInviteLink(),
@ -145,7 +146,8 @@ class _CopyLinkButton extends StatelessWidget {
);
} else {
showToastNotification(
message: LocaleKeys.shareAction_copyLinkFailed.tr(),
message: 'You haven\'t generated an invite link yet.',
type: ToastificationType.error,
);
}
},

View File

@ -10,6 +10,7 @@ import 'package:appflowy/workspace/presentation/settings/widgets/members/inivita
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
@ -318,6 +319,7 @@ class _MemberListHeader extends StatelessWidget {
return Row(
children: [
Expanded(
flex: 4,
child: Text(
LocaleKeys.settings_appearance_members_user.tr(),
style: theme.textStyle.body.standard(
@ -326,6 +328,7 @@ class _MemberListHeader extends StatelessWidget {
),
),
Expanded(
flex: 2,
child: Text(
LocaleKeys.settings_appearance_members_role.tr(),
style: theme.textStyle.body.standard(
@ -334,6 +337,7 @@ class _MemberListHeader extends StatelessWidget {
),
),
Expanded(
flex: 3,
child: Text(
LocaleKeys.settings_accountPage_email_title.tr(),
style: theme.textStyle.body.standard(
@ -364,14 +368,39 @@ class _MemberItem extends StatelessWidget {
return Row(
children: [
Expanded(
child: Text(
member.name,
style: theme.textStyle.body.enhanced(
color: theme.textColorScheme.primary,
),
flex: 4,
child: Row(
children: [
UserAvatar(
iconUrl: member.avatarUrl,
name: member.name,
size: 24,
fontSize: 12,
emojiFontSize: 20,
),
HSpace(8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
member.name,
style: theme.textStyle.body.enhanced(
color: theme.textColorScheme.primary,
),
),
Text(
_formatJoinedDate(member.joinedAt.toInt()),
style: theme.textStyle.caption.standard(
color: theme.textColorScheme.secondary,
),
),
],
),
],
),
),
Expanded(
flex: 2,
child: member.role.isOwner || !myRole.canUpdate
? Text(
member.role.description,
@ -384,6 +413,7 @@ class _MemberItem extends StatelessWidget {
),
),
Expanded(
flex: 3,
child: FlowyTooltip(
message: member.email,
child: Text(
@ -403,6 +433,11 @@ class _MemberItem extends StatelessWidget {
],
);
}
String _formatJoinedDate(int joinedAt) {
final date = DateTime.fromMillisecondsSinceEpoch(joinedAt * 1000);
return 'Joined on ${DateFormat('MMM d, y').format(date)}';
}
}
enum _MemberMoreAction {
@ -428,7 +463,7 @@ class _MemberMoreActionList extends StatelessWidget {
return FlowyButton(
useIntrinsicWidth: true,
text: const FlowySvg(
FlowySvgs.three_dots_vertical_s,
FlowySvgs.three_dots_s,
),
onTap: () {
controller.show();

View File

@ -4,6 +4,7 @@ import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart';
@ -25,28 +26,27 @@ class SettingsMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = AppFlowyTheme.of(context);
// Column > Expanded for full size no matter the content
return Column(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8) +
const EdgeInsets.only(left: 8, right: 4),
child: DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
color: theme.backgroundColorScheme.secondary,
borderRadius: const BorderRadiusDirectional.only(
topStart: Radius.circular(8),
bottomStart: Radius.circular(8),
),
),
child: SingleChildScrollView(
// Right padding is added to make the scrollbar centered
// in the space between the menu and the content
padding: const EdgeInsets.only(right: 4) +
const EdgeInsets.symmetric(vertical: 16),
padding: EdgeInsets.symmetric(
vertical: 24,
horizontal: theme.spacing.l,
),
physics: const ClampingScrollPhysics(),
child: SeparatedColumn(
separatorBuilder: () => const VSpace(16),
separatorBuilder: () => VSpace(theme.spacing.xs),
children: [
SettingsMenuElement(
page: SettingsPage.account,

View File

@ -1,8 +1,6 @@
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
class SettingsMenuElement extends StatelessWidget {
@ -23,42 +21,67 @@ class SettingsMenuElement extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowyHover(
isSelected: () => page == selectedPage,
resetHoverOnRebuild: false,
style: HoverStyle(
hoverColor: AFThemeExtension.of(context).greyHover,
borderRadius: BorderRadius.circular(4),
),
builder: (_, isHovering) => ListTile(
dense: true,
leading: iconWidget(
isHovering || page == selectedPage
? Theme.of(context).colorScheme.onSurface
: AFThemeExtension.of(context).textColor,
),
onTap: () => changeSelectedPage(page),
selected: page == selectedPage,
selectedColor: Theme.of(context).colorScheme.onSurface,
selectedTileColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
minLeadingWidth: 0,
title: FlowyText.medium(
label,
fontSize: FontSizes.s14,
overflow: TextOverflow.ellipsis,
color: page == selectedPage
? Theme.of(context).colorScheme.onSurface
: null,
),
),
final theme = AppFlowyTheme.of(context);
return AFBaseButton(
onTap: () => changeSelectedPage(page),
padding: EdgeInsets.all(theme.spacing.m),
borderRadius: theme.borderRadius.m,
borderColor: (_, __, ___, ____) => theme.fillColorScheme.transparent,
backgroundColor: (_, isHovering, __) {
if (isHovering) {
return theme.fillColorScheme.primaryAlpha5;
} else if (page == selectedPage) {
return theme.fillColorScheme.themeSelect;
}
return theme.fillColorScheme.transparent;
},
builder: (_, __, ___) {
return Row(
children: [
icon,
HSpace(theme.spacing.m),
Text(
label,
style: theme.textStyle.body.standard(
color: theme.textColorScheme.primary,
),
),
],
);
},
);
}
Widget iconWidget(Color color) => IconTheme(
data: IconThemeData(color: color),
child: icon,
);
// return FlowyHover(
// isSelected: () => page == selectedPage,
// resetHoverOnRebuild: false,
// style: HoverStyle(
// hoverColor: AFThemeExtension.of(context).greyHover,
// borderRadius: BorderRadius.circular(4),
// ),
// builder: (_, isHovering) => ListTile(
// dense: true,
// leading: iconWidget(
// isHovering || page == selectedPage
// ? Theme.of(context).colorScheme.onSurface
// : AFThemeExtension.of(context).textColor,
// ),
// onTap: () => changeSelectedPage(page),
// selected: page == selectedPage,
// selectedColor: Theme.of(context).colorScheme.onSurface,
// selectedTileColor: Theme.of(context).colorScheme.primary,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(5),
// ),
// minLeadingWidth: 0,
// title: FlowyText.medium(
// label,
// fontSize: FontSizes.s14,
// overflow: TextOverflow.ellipsis,
// color: page == selectedPage
// ? Theme.of(context).colorScheme.onSurface
// : null,
// ),
// ),
// );
}

View File

@ -17,12 +17,14 @@ class UserAvatar extends StatelessWidget {
required this.fontSize,
this.isHovering = false,
this.decoration,
this.emojiFontSize,
});
final String iconUrl;
final String name;
final double size;
final double fontSize;
final double? emojiFontSize;
final Decoration? decoration;
// If true, a border will be applied on top of the avatar
@ -127,7 +129,10 @@ class UserAvatar extends StatelessWidget {
FlowySvgData('emoji/$iconUrl'),
blendMode: null,
)
: FlowyText.emoji(iconUrl, fontSize: fontSize),
: FlowyText.emoji(
iconUrl,
fontSize: emojiFontSize ?? fontSize,
),
),
),
),

View File

@ -4,7 +4,7 @@ description: Bring projects, wikis, and teams together with AI. AppFlowy is an
your data. The best open source alternative to Notion.
publish_to: "none"
version: 0.8.9
version: 0.9.0
environment:
flutter: ">=3.27.4"

View File

@ -1353,7 +1353,7 @@
"generateANewLink": "generate a new link",
"inviteMemberByEmail": "Invite member by email",
"inviteMemberHintText": "Invite by email",
"resetInviteLink": "Reset the invite link",
"resetInviteLink": "Reset the invite link?",
"resetInviteLinkDescription": "Resetting will deactivate the current link for all space members and generate a new one. The previous link can only be managed through the",
"adminPanel": "Admin Panel",
"reset": "Reset",

View File

@ -493,7 +493,7 @@ checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"anyhow",
"bincode",
@ -513,7 +513,7 @@ dependencies = [
[[package]]
name = "appflowy-ai-client"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"anyhow",
"bytes",
@ -1159,7 +1159,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"again",
"anyhow",
@ -1214,7 +1214,7 @@ dependencies = [
[[package]]
name = "client-api-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"collab-entity",
"collab-rt-entity",
@ -1227,7 +1227,7 @@ dependencies = [
[[package]]
name = "client-websocket"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"futures-channel",
"futures-util",
@ -1499,7 +1499,7 @@ dependencies = [
[[package]]
name = "collab-rt-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"anyhow",
"bincode",
@ -1521,7 +1521,7 @@ dependencies = [
[[package]]
name = "collab-rt-protocol"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"anyhow",
"async-trait",
@ -1969,7 +1969,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"bincode",
"bytes",
@ -3426,7 +3426,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"anyhow",
"getrandom 0.2.10",
@ -3441,7 +3441,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"app-error",
"jsonwebtoken",
@ -4065,7 +4065,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"anyhow",
"bytes",
@ -6643,7 +6643,7 @@ dependencies = [
[[package]]
name = "shared-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [
"anyhow",
"app-error",

View File

@ -108,8 +108,8 @@ af-local-ai = { version = "0.1" }
# Run the script.add_workspace_members:
# scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" }
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "954c323" }
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "954c323" }
[profile.dev]
opt-level = 0

View File

@ -71,6 +71,7 @@ pub fn from_af_workspace_member(member: AFWorkspaceMember) -> WorkspaceMember {
role: from_af_role(member.role),
name: member.name,
avatar_url: member.avatar_url,
joined_at: member.joined_at.map(|dt| dt.timestamp()),
}
}

View File

@ -248,6 +248,7 @@ impl UserCloudService for LocalServerUserServiceImpl {
uid,
workspace_id: workspace_id.to_string(),
updated_at: chrono::Utc::now().naive_utc(),
joined_at: None,
};
let member = WorkspaceMember::from(row.clone());

View File

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
ALTER TABLE workspace_members_table
DROP COLUMN joined_at;

View File

@ -0,0 +1,3 @@
-- Your SQL goes here
ALTER TABLE workspace_members_table
ADD COLUMN joined_at BIGINT DEFAULT NULL;

View File

@ -120,6 +120,7 @@ diesel::table! {
uid -> BigInt,
workspace_id -> Text,
updated_at -> Timestamp,
joined_at -> Nullable<BigInt>,
}
}
@ -132,16 +133,16 @@ diesel::table! {
}
diesel::allow_tables_to_appear_in_same_query!(
af_collab_metadata,
chat_local_setting_table,
chat_message_table,
chat_table,
collab_snapshot,
upload_file_part,
upload_file_table,
user_data_migration_records,
user_table,
user_workspace_table,
workspace_members_table,
workspace_setting_table,
af_collab_metadata,
chat_local_setting_table,
chat_message_table,
chat_table,
collab_snapshot,
upload_file_part,
upload_file_table,
user_data_migration_records,
user_table,
user_workspace_table,
workspace_members_table,
workspace_setting_table,
);

View File

@ -370,6 +370,7 @@ pub struct WorkspaceMember {
pub role: Role,
pub name: String,
pub avatar_url: Option<String>,
pub joined_at: Option<i64>,
}
/// represent the user awareness object id for the workspace.

View File

@ -16,6 +16,7 @@ pub struct WorkspaceMemberTable {
pub uid: i64,
pub workspace_id: String,
pub updated_at: chrono::NaiveDateTime,
pub joined_at: Option<i64>,
}
impl From<WorkspaceMemberTable> for WorkspaceMember {
@ -25,6 +26,7 @@ impl From<WorkspaceMemberTable> for WorkspaceMember {
role: Role::from(value.role),
name: value.name,
avatar_url: value.avatar_url,
joined_at: value.joined_at,
}
}
}

View File

@ -111,6 +111,7 @@ pub fn insert_local_workspace(
uid,
workspace_id: workspace_id.to_string(),
updated_at: chrono::Utc::now().naive_utc(),
joined_at: None,
};
upsert_user_workspace(uid, AuthType::Local, user_workspace.clone(), conn)?;

View File

@ -24,6 +24,9 @@ pub struct WorkspaceMemberPB {
#[pb(index = 4, one_of)]
pub avatar_url: Option<String>,
#[pb(index = 5, one_of)]
pub joined_at: Option<i64>,
}
impl From<WorkspaceMember> for WorkspaceMemberPB {
@ -33,6 +36,7 @@ impl From<WorkspaceMember> for WorkspaceMemberPB {
name: value.name,
role: value.role.into(),
avatar_url: value.avatar_url,
joined_at: value.joined_at,
}
}
}

View File

@ -673,6 +673,7 @@ impl UserManager {
role: member_record.role.into(),
name: member_record.name,
avatar_url: member_record.avatar_url,
joined_at: member_record.joined_at,
});
}
@ -703,6 +704,7 @@ impl UserManager {
uid,
workspace_id: workspace_id.to_string(),
updated_at: Utc::now().naive_utc(),
joined_at: member.joined_at,
};
let mut db = self.authenticate_user.get_sqlite_connection(uid)?;