22using Microsoft . AspNetCore . Authorization ;
33using Microsoft . AspNetCore . Mvc ;
44
5+ using XtremeIdiots . Portal . Repository . Abstractions . Models . V1 . Notifications ;
56using XtremeIdiots . Portal . Repository . Api . Client . V1 ;
67using XtremeIdiots . Portal . Web . Auth . Constants ;
8+ using XtremeIdiots . Portal . Web . Extensions ;
9+ using XtremeIdiots . Portal . Web . Models ;
710
811namespace XtremeIdiots . Portal . Web . Controllers ;
912
1013/// <summary>
11- /// Handles user profile management operations
14+ /// Handles user profile management operations including notification preferences
1215/// </summary>
1316/// <remarks>
1417/// Initializes a new instance of the ProfileController
1518/// </remarks>
19+ /// <param name="repositoryApiClient">Client for repository API operations</param>
1620/// <param name="telemetryClient">Application Insights telemetry client</param>
1721/// <param name="logger">Logger instance for this controller</param>
1822/// <param name="configuration">Application configuration</param>
1923[ Authorize ( Policy = AuthPolicies . AccessProfile ) ]
2024public class ProfileController (
25+ IRepositoryApiClient repositoryApiClient ,
2126 TelemetryClient telemetryClient ,
2227 ILogger < ProfileController > logger ,
2328 IConfiguration configuration ) : BaseController ( telemetryClient , logger , configuration )
2429{
25- // No additional dependencies required for current actions
26-
2730 /// <summary>
2831 /// Displays the user profile management page
2932 /// </summary>
@@ -34,4 +37,167 @@ public async Task<IActionResult> Manage(CancellationToken cancellationToken = de
3437 {
3538 return await ExecuteWithErrorHandlingAsync ( ( ) => Task . FromResult < IActionResult > ( View ( ) ) , nameof ( Manage ) ) . ConfigureAwait ( false ) ;
3639 }
40+
41+ /// <summary>
42+ /// Displays the notification preferences page where users can configure delivery channels
43+ /// </summary>
44+ /// <param name="cancellationToken">Cancellation token for the async operation</param>
45+ /// <returns>The notification preferences view</returns>
46+ [ HttpGet ]
47+ public async Task < IActionResult > Notifications ( CancellationToken cancellationToken = default )
48+ {
49+ return await ExecuteWithErrorHandlingAsync ( async ( ) =>
50+ {
51+ var xtremeIdiotsId = User . XtremeIdiotsId ( ) ;
52+ if ( string . IsNullOrEmpty ( xtremeIdiotsId ) )
53+ return RedirectToAction ( nameof ( Manage ) ) ;
54+
55+ var userProfileResponse = await repositoryApiClient . UserProfiles . V1
56+ . GetUserProfileByXtremeIdiotsId ( xtremeIdiotsId ) . ConfigureAwait ( false ) ;
57+
58+ if ( userProfileResponse . IsNotFound || userProfileResponse . Result ? . Data is null )
59+ return RedirectToAction ( nameof ( Manage ) ) ;
60+
61+ var userProfile = userProfileResponse . Result . Data ;
62+
63+ var typesResponse = await repositoryApiClient . NotificationTypes . V1
64+ . GetNotificationTypes ( cancellationToken ) . ConfigureAwait ( false ) ;
65+
66+ var prefsResponse = await repositoryApiClient . NotificationPreferences . V1
67+ . GetNotificationPreferences ( userProfile . UserProfileId , cancellationToken ) . ConfigureAwait ( false ) ;
68+
69+ var notificationTypes = ( typesResponse . Result ? . Data ? . Items ?? [ ] )
70+ . Select ( t => new NotificationTypeViewModel (
71+ t . NotificationTypeId , t . DisplayName , t . Description ,
72+ t . SupportsInSite , t . SupportsEmail ,
73+ ( t . DefaultChannels ?? "" ) . Contains ( "InSite" ) ,
74+ ( t . DefaultChannels ?? "" ) . Contains ( "Email" ) ) )
75+ . ToList ( ) ;
76+
77+ var preferences = ( prefsResponse . Result ? . Data ? . Items ?? [ ] )
78+ . Select ( p => new NotificationPreferenceViewModel (
79+ p . NotificationTypeId , p . InSiteEnabled , p . EmailEnabled ) )
80+ . ToList ( ) ;
81+
82+ var model = new NotificationPreferencesPageViewModel (
83+ userProfile . UserProfileId ,
84+ notificationTypes ,
85+ preferences ) ;
86+
87+ return View ( model ) ;
88+ } , nameof ( Notifications ) ) . ConfigureAwait ( false ) ;
89+ }
90+
91+ /// <summary>
92+ /// Saves notification preferences submitted from the preferences form
93+ /// </summary>
94+ /// <param name="userProfileId">The user profile ID to save preferences for</param>
95+ /// <param name="cancellationToken">Cancellation token for the async operation</param>
96+ /// <returns>Redirects back to the notification preferences page</returns>
97+ [ HttpPost ]
98+ [ ValidateAntiForgeryToken ]
99+ public async Task < IActionResult > Notifications ( Guid userProfileId , CancellationToken cancellationToken = default )
100+ {
101+ return await ExecuteWithErrorHandlingAsync ( async ( ) =>
102+ {
103+ var form = await Request . ReadFormAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
104+ var typeIds = form . Keys
105+ . Where ( k => k . StartsWith ( "insite_" , StringComparison . Ordinal ) || k . StartsWith ( "email_" , StringComparison . Ordinal ) )
106+ . Select ( k => k . Split ( '_' , 2 ) [ 1 ] )
107+ . Distinct ( )
108+ . ToList ( ) ;
109+
110+ var preferences = new List < NotificationPreferenceViewModel > ( ) ;
111+
112+ foreach ( var typeId in typeIds )
113+ {
114+ preferences . Add ( new NotificationPreferenceViewModel (
115+ typeId ,
116+ InSiteEnabled : form . ContainsKey ( $ "insite_{ typeId } ") ,
117+ EmailEnabled : form . ContainsKey ( $ "email_{ typeId } ") ) ) ;
118+ }
119+
120+ // Handle notification types where both checkboxes are unchecked (not present in form)
121+ var allTypesResponse = await repositoryApiClient . NotificationTypes . V1
122+ . GetNotificationTypes ( cancellationToken ) . ConfigureAwait ( false ) ;
123+ var allTypeIds = ( allTypesResponse . Result ? . Data ? . Items ?? [ ] ) . Select ( t => t . NotificationTypeId ) ;
124+
125+ foreach ( var typeId in allTypeIds )
126+ {
127+ if ( ! typeIds . Contains ( typeId ) )
128+ {
129+ preferences . Add ( new NotificationPreferenceViewModel (
130+ typeId ,
131+ InSiteEnabled : false ,
132+ EmailEnabled : false ) ) ;
133+ }
134+ }
135+
136+ var editDtos = preferences . Select ( p => new EditNotificationPreferenceDto ( p . NotificationTypeId )
137+ {
138+ InSiteEnabled = p . InSiteEnabled ,
139+ EmailEnabled = p . EmailEnabled
140+ } ) . ToList ( ) ;
141+
142+ await repositoryApiClient . NotificationPreferences . V1
143+ . UpdateNotificationPreferences ( userProfileId , editDtos , cancellationToken ) . ConfigureAwait ( false ) ;
144+
145+ this . AddAlertSuccess ( "Notification preferences saved successfully." ) ;
146+ return RedirectToAction ( nameof ( Notifications ) ) ;
147+ } , nameof ( Notifications ) ) . ConfigureAwait ( false ) ;
148+ }
149+
150+ /// <summary>
151+ /// Displays all notifications for the current user with pagination
152+ /// </summary>
153+ /// <param name="page">Page number (1-based)</param>
154+ /// <param name="cancellationToken">Cancellation token for the async operation</param>
155+ /// <returns>The all notifications view</returns>
156+ [ HttpGet ]
157+ public async Task < IActionResult > AllNotifications ( int page = 1 , CancellationToken cancellationToken = default )
158+ {
159+ return await ExecuteWithErrorHandlingAsync ( async ( ) =>
160+ {
161+ var xtremeIdiotsId = User . XtremeIdiotsId ( ) ;
162+ if ( string . IsNullOrEmpty ( xtremeIdiotsId ) )
163+ return RedirectToAction ( nameof ( Manage ) ) ;
164+
165+ var userProfileResponse = await repositoryApiClient . UserProfiles . V1
166+ . GetUserProfileByXtremeIdiotsId ( xtremeIdiotsId ) . ConfigureAwait ( false ) ;
167+
168+ if ( userProfileResponse . IsNotFound || userProfileResponse . Result ? . Data is null )
169+ return RedirectToAction ( nameof ( Manage ) ) ;
170+
171+ const int pageSize = 20 ;
172+ var skipEntries = ( Math . Max ( 1 , page ) - 1 ) * pageSize ;
173+ var userProfileId = userProfileResponse . Result . Data . UserProfileId ;
174+
175+ var notificationsResponse = await repositoryApiClient . Notifications . V1
176+ . GetNotifications ( userProfileId , null , skipEntries , pageSize , null , cancellationToken ) . ConfigureAwait ( false ) ;
177+
178+ var unreadCountResponse = await repositoryApiClient . Notifications . V1
179+ . GetUnreadNotificationCount ( userProfileId , cancellationToken ) . ConfigureAwait ( false ) ;
180+
181+ var items = notificationsResponse . Result ? . Data ? . Items ?? [ ] ;
182+ var totalCount = notificationsResponse . Result ? . Pagination ? . TotalCount ?? 0 ;
183+ var unreadCount = unreadCountResponse . Result ? . Data ?? 0 ;
184+ var totalPages = Math . Max ( 1 , ( int ) Math . Ceiling ( ( double ) totalCount / pageSize ) ) ;
185+
186+ var notifications = items . Select ( n => new NotificationViewModel (
187+ n . NotificationId , n . Title , n . Message ,
188+ "fa-solid fa-bell" ,
189+ n . CreatedAt , n . IsRead ,
190+ n . ActionUrl ) ) . ToList ( ) ;
191+
192+ var model = new AllNotificationsPageViewModel (
193+ notifications ,
194+ CurrentPage : Math . Max ( 1 , page ) ,
195+ TotalPages : totalPages ,
196+ TotalCount : totalCount ,
197+ UnreadCount : unreadCount ) ;
198+
199+ return View ( model ) ;
200+ } , nameof ( AllNotifications ) ) . ConfigureAwait ( false ) ;
201+ }
202+
37203}
0 commit comments