diff --git a/app/lib/frontend/handlers/pubapi.client.dart b/app/lib/frontend/handlers/pubapi.client.dart index 1dadba4dd..36f0b0322 100644 --- a/app/lib/frontend/handlers/pubapi.client.dart +++ b/app/lib/frontend/handlers/pubapi.client.dart @@ -599,8 +599,8 @@ class PubApiClient { )); } - Future<_i3.Message> postReport(_i4.ReportForm payload) async { - return _i3.Message.fromJson(await _client.requestJson( + Future<_i3.FormResponse> postReport(_i4.ReportForm payload) async { + return _i3.FormResponse.fromJson(await _client.requestJson( verb: 'post', path: '/api/report', body: payload.toJson(), diff --git a/app/lib/frontend/handlers/pubapi.dart b/app/lib/frontend/handlers/pubapi.dart index 90bb03779..ef3aaa02d 100644 --- a/app/lib/frontend/handlers/pubapi.dart +++ b/app/lib/frontend/handlers/pubapi.dart @@ -584,8 +584,7 @@ class PubApi { listAdvisoriesForPackage(request, package); @EndPoint.post('/api/report') - Future postReport(Request request, ReportForm body) async { - final message = await processReportPageHandler(request, body); - return Message(message: message); + Future postReport(Request request, ReportForm body) async { + return await processReportPageHandler(request, body); } } diff --git a/app/lib/frontend/handlers/report.dart b/app/lib/frontend/handlers/report.dart index 06dcd6f3e..b8905d3ee 100644 --- a/app/lib/frontend/handlers/report.dart +++ b/app/lib/frontend/handlers/report.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:_pub_shared/data/account_api.dart'; +import 'package:_pub_shared/data/package_api.dart'; import 'package:clock/clock.dart'; import 'package:pub_dev/admin/backend.dart'; import 'package:pub_dev/shared/configuration.dart'; @@ -35,6 +36,23 @@ Future reportPageHandler(shelf.Request request) async { return notFoundHandler(request); } + final feedback = request.requestedUri.queryParameters['feedback']; + if (feedback != null) { + switch (feedback) { + case 'report-submitted': + return htmlResponse(renderReportFeedback( + title: 'Report submitted', + message: 'The report has been submitted successfully.', + )); + case 'appeal-submitted': + return htmlResponse(renderReportFeedback( + title: 'Appeal submitted', + message: 'The appeal has been submitted successfully.', + )); + } + return notFoundHandler(request); + } + final caseId = request.requestedUri.queryParameters['appeal']; final mc = await _loadAndVerifyCase(caseId); @@ -134,7 +152,7 @@ Future _verifyCaseSubject( } /// Handles POST /api/report -Future processReportPageHandler( +Future processReportPageHandler( shelf.Request request, ReportForm form) async { if (!requestContext.experimentalFlags.isReportPageEnabled) { throw NotFoundException('Experimental flag is not enabled.'); @@ -233,5 +251,15 @@ Future processReportPageHandler( bodyText: bodyText, )); - return 'The $kind was submitted successfully.'; + final redirectTo = request.requestedUri.replace( + path: '/report', + queryParameters: { + 'feedback': '$kind-submitted', + }, + ).toString(); + + return FormResponse( + message: null, // no modal feedback + redirectTo: redirectTo, + ); } diff --git a/app/lib/frontend/templates/report.dart b/app/lib/frontend/templates/report.dart index cacad00eb..f5837bb2d 100644 --- a/app/lib/frontend/templates/report.dart +++ b/app/lib/frontend/templates/report.dart @@ -16,6 +16,22 @@ const _subjectKindLabels = { ModerationSubjectKind.publisher: 'publisher', }; +/// Renders the feedback page with a simple paragraph of [message]. +String renderReportFeedback({ + required String title, + required String message, +}) { + return renderLayoutPage( + PageType.standalone, + d.fragment([ + d.h1(text: title), + d.p(text: message), + ]), + title: title, + noIndex: true, + ); +} + /// Renders the create publisher page. String renderReportPage({ SessionData? sessionData, diff --git a/pkg/_pub_shared/lib/data/package_api.dart b/pkg/_pub_shared/lib/data/package_api.dart index fddeeee25..f58cdf27a 100644 --- a/pkg/_pub_shared/lib/data/package_api.dart +++ b/pkg/_pub_shared/lib/data/package_api.dart @@ -190,6 +190,23 @@ class Message { Map toJson() => _$MessageToJson(this); } +/// A message wrapper for pub client API compatibility. +@JsonSerializable(includeIfNull: false) +class FormResponse { + final String? message; + final String? redirectTo; + + FormResponse({ + required this.message, + required this.redirectTo, + }); + + factory FormResponse.fromJson(Map json) => + _$FormResponseFromJson(json); + + Map toJson() => _$FormResponseToJson(this); +} + /// Used in `pub` client for finding which versions exist. /// (`listVersions` method in pubapi) @JsonSerializable(includeIfNull: false) diff --git a/pkg/_pub_shared/lib/data/package_api.g.dart b/pkg/_pub_shared/lib/data/package_api.g.dart index 43e14661f..d8199a7c4 100644 --- a/pkg/_pub_shared/lib/data/package_api.g.dart +++ b/pkg/_pub_shared/lib/data/package_api.g.dart @@ -155,6 +155,25 @@ Map _$MessageToJson(Message instance) => { 'message': instance.message, }; +FormResponse _$FormResponseFromJson(Map json) => FormResponse( + message: json['message'] as String?, + redirectTo: json['redirectTo'] as String?, + ); + +Map _$FormResponseToJson(FormResponse instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('message', instance.message); + writeNotNull('redirectTo', instance.redirectTo); + return val; +} + PackageData _$PackageDataFromJson(Map json) => PackageData( name: json['name'] as String, isDiscontinued: json['isDiscontinued'] as bool?, diff --git a/pkg/_pub_shared/lib/src/pubapi.client.dart b/pkg/_pub_shared/lib/src/pubapi.client.dart index 1dadba4dd..36f0b0322 100644 --- a/pkg/_pub_shared/lib/src/pubapi.client.dart +++ b/pkg/_pub_shared/lib/src/pubapi.client.dart @@ -599,8 +599,8 @@ class PubApiClient { )); } - Future<_i3.Message> postReport(_i4.ReportForm payload) async { - return _i3.Message.fromJson(await _client.requestJson( + Future<_i3.FormResponse> postReport(_i4.ReportForm payload) async { + return _i3.FormResponse.fromJson(await _client.requestJson( verb: 'post', path: '/api/report', body: payload.toJson(), diff --git a/pkg/pub_integration/test/report_test.dart b/pkg/pub_integration/test/report_test.dart index b6e1bc17b..051299742 100644 --- a/pkg/pub_integration/test/report_test.dart +++ b/pkg/pub_integration/test/report_test.dart @@ -65,10 +65,10 @@ void main() { await page.waitFocusAndType('#report-email', 'reporter@pub.dev'); await page.waitFocusAndType( '#report-message', 'Huston, we have a problem.'); - await page.waitAndClick('#report-submit', waitForOneResponse: true); - expect(await page.content, - contains('The report was submitted successfully.')); - await page.waitAndClickOnDialogOk(); + await page.waitAndClick('#report-submit'); + await page.waitForNavigation(); + expect( + await page.content, contains('has been submitted successfully')); }, ); @@ -197,10 +197,9 @@ void main() { await page.waitFocusAndType( '#report-message', 'Huston, I have a different idea.'); - await page.waitAndClick('#report-submit', waitForOneResponse: true); - expect(await page.content, - contains('The appeal was submitted successfully.')); - await page.waitAndClickOnDialogOk(); + await page.waitAndClick('#report-submit'); + await page.waitForNavigation(); + expect(await page.content, contains('has been submitted successfully')); }); final appealEmail = await supportUser.readLatestEmail(); diff --git a/pkg/web_app/lib/src/admin_pages.dart b/pkg/web_app/lib/src/admin_pages.dart index 1f6773383..3912eee53 100644 --- a/pkg/web_app/lib/src/admin_pages.dart +++ b/pkg/web_app/lib/src/admin_pages.dart @@ -52,11 +52,23 @@ void _initGenericForm() { fn: () => api_client.sendJson(verb: 'POST', path: endpoint, body: body), successMessage: null, - onSuccess: (result) async { - final message = - result == null ? null : result['message']?.toString(); - await modalMessage('Success', text(message ?? 'OK.')); - window.location.reload(); + onSuccess: (r) async { + final result = r ?? {}; + final redirectTo = result['redirectTo']?.toString(); + + // We shall display a minimal feedback when both the redirect and the message is omitted. + final message = result['message']?.toString() ?? + (redirectTo == null ? 'Success. The page will reload.' : null); + + if (message != null) { + await modalMessage('Success', text(message)); + } + + if (redirectTo != null) { + window.location.href = redirectTo; + } else { + window.location.reload(); + } }, ); });