feat(recipes): add recipe visibility and sharing features
- Implemented functionality to set recipe visibility (public/private) with appropriate checks for user permissions. - Added ability to share recipes with other users, including validation for existing users and permissions. - Introduced new DTOs for setting visibility and sharing recipes. - Updated RecipesController and RecipesService to handle new endpoints for visibility and sharing. - Enhanced inventory preview to consider user permissions and shared recipes. - Updated front-end to support new sharing and visibility features, including UI changes for recipe detail and admin user management.
This commit is contained in:
@@ -49,6 +49,19 @@ class AdminRepository {
|
||||
return UserAdmin.fromJson(data);
|
||||
}
|
||||
|
||||
Future<UserAdmin> setRecipeSharing(int userId, {required bool canShareRecipes}) async {
|
||||
final token = await _token();
|
||||
final data = await guardedApiCall(
|
||||
_ref,
|
||||
() => _apiClient.patchJson(
|
||||
UserApiPaths.setRecipeSharing(userId),
|
||||
body: {'canShareRecipes': canShareRecipes},
|
||||
token: token,
|
||||
),
|
||||
);
|
||||
return UserAdmin.fromJson(data);
|
||||
}
|
||||
|
||||
Future<void> updateEmail(int userId, String email) async {
|
||||
final token = await _token();
|
||||
await guardedApiCall(
|
||||
|
||||
@@ -7,6 +7,7 @@ class UserAdmin {
|
||||
final String? lastName;
|
||||
final String role;
|
||||
final bool isPremium;
|
||||
final bool canShareRecipes;
|
||||
final DateTime? createdAt;
|
||||
|
||||
const UserAdmin({
|
||||
@@ -17,6 +18,7 @@ class UserAdmin {
|
||||
this.lastName,
|
||||
required this.role,
|
||||
required this.isPremium,
|
||||
required this.canShareRecipes,
|
||||
this.createdAt,
|
||||
});
|
||||
|
||||
@@ -28,6 +30,7 @@ class UserAdmin {
|
||||
lastName: json['lastName'] as String?,
|
||||
role: json['role'] as String? ?? 'user',
|
||||
isPremium: json['isPremium'] as bool? ?? false,
|
||||
canShareRecipes: json['canShareRecipes'] as bool? ?? true,
|
||||
createdAt: json['createdAt'] != null ? DateTime.tryParse(json['createdAt'] as String) : null,
|
||||
);
|
||||
|
||||
|
||||
@@ -81,6 +81,26 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _toggleRecipeSharing(UserAdmin user) async {
|
||||
final newValue = !user.canShareRecipes;
|
||||
final confirmed = await _confirm(
|
||||
context,
|
||||
newValue ? 'Tillåt receptdelning' : 'Blockera receptdelning',
|
||||
'${newValue ? 'Tillåt' : 'Blockera'} receptdelning för ${user.username}?',
|
||||
);
|
||||
if (!confirmed || !mounted) return;
|
||||
try {
|
||||
await ref
|
||||
.read(adminRepositoryProvider)
|
||||
.setRecipeSharing(user.id, canShareRecipes: newValue);
|
||||
if (!mounted) return;
|
||||
_load();
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
_showError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _resetPassword(UserAdmin user) async {
|
||||
final confirmed = await _confirm(
|
||||
context,
|
||||
@@ -319,6 +339,7 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
user: _users[i],
|
||||
onChangeRole: () => _changeRole(_users[i]),
|
||||
onTogglePremium: () => _togglePremium(_users[i]),
|
||||
onToggleRecipeSharing: () => _toggleRecipeSharing(_users[i]),
|
||||
onEditEmail: () => _editEmail(_users[i]),
|
||||
onResetPassword: () => _resetPassword(_users[i]),
|
||||
onDelete: () => _deleteUser(_users[i]),
|
||||
@@ -364,6 +385,7 @@ class _UserTile extends StatelessWidget {
|
||||
final UserAdmin user;
|
||||
final VoidCallback onChangeRole;
|
||||
final VoidCallback onTogglePremium;
|
||||
final VoidCallback onToggleRecipeSharing;
|
||||
final VoidCallback onEditEmail;
|
||||
final VoidCallback onResetPassword;
|
||||
final VoidCallback onDelete;
|
||||
@@ -372,6 +394,7 @@ class _UserTile extends StatelessWidget {
|
||||
required this.user,
|
||||
required this.onChangeRole,
|
||||
required this.onTogglePremium,
|
||||
required this.onToggleRecipeSharing,
|
||||
required this.onEditEmail,
|
||||
required this.onResetPassword,
|
||||
required this.onDelete,
|
||||
@@ -418,6 +441,16 @@ class _UserTile extends StatelessWidget {
|
||||
backgroundColor: theme.colorScheme.tertiaryContainer,
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 4),
|
||||
Chip(
|
||||
label: Text(user.canShareRecipes ? 'Delning: På' : 'Delning: Av'),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
labelStyle: theme.textTheme.labelSmall,
|
||||
backgroundColor: user.canShareRecipes
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: theme.colorScheme.errorContainer,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -432,6 +465,9 @@ class _UserTile extends StatelessWidget {
|
||||
case 'premium':
|
||||
onTogglePremium();
|
||||
break;
|
||||
case 'sharing':
|
||||
onToggleRecipeSharing();
|
||||
break;
|
||||
case 'email':
|
||||
onEditEmail();
|
||||
break;
|
||||
@@ -454,6 +490,14 @@ class _UserTile extends StatelessWidget {
|
||||
value: 'premium',
|
||||
child: Text(user.isPremium ? 'Ta bort Premium' : 'Ge Premium'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'sharing',
|
||||
child: Text(
|
||||
user.canShareRecipes
|
||||
? 'Blockera receptdelning'
|
||||
: 'Tillåt receptdelning',
|
||||
),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: 'email',
|
||||
child: Text('Ändra e-post'),
|
||||
|
||||
Reference in New Issue
Block a user