Merge pull request #1321 from PicPeak/fix/1317-usage-portal-copy

feat(usage): plain link to the public usage portal, German opt-in copy
This commit is contained in:
Paul Nothaft
2026-09-07 09:19:32 +02:00
committed by GitHub
4 changed files with 54 additions and 9 deletions
@@ -317,3 +317,30 @@ it('states in the consent dialog that the connection only runs outwards', async
await screen.findByText('productUsage.sectionOneWay'); await screen.findByText('productUsage.sectionOneWay');
await screen.findByText('productUsage.oneWay'); await screen.findByText('productUsage.oneWay');
}); });
// The portal is public. Reaching it must not depend on having joined or on
// the 15-minute voting session that `connect` creates — an operator deciding
// whether to participate should be able to look at it first.
describe('the plain usage portal link', () => {
it('opens the collector in a new tab before participation, without a session', async () => {
mount();
const link = await screen.findByRole('link', { name: 'productUsage.openUsagePortal' });
expect(link).toHaveAttribute('href', 'https://usage.picpeak.app');
expect(link).toHaveAttribute('target', '_blank');
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
expect(service.portalSession).not.toHaveBeenCalled();
// The session-bound link is a different thing and stays behind `connect`.
expect(screen.queryByText('productUsage.openPortal')).toBeNull();
});
it('is not offered when the collector URL is unusable', async () => {
vi.mocked(service.status).mockResolvedValue({
...status,
collector_url: null,
collector_error: 'INVALID_COLLECTOR_URL'
});
mount();
await screen.findByText('productUsage.invalidCollectorUrl');
expect(screen.queryByRole('link', { name: 'productUsage.openUsagePortal' })).toBeNull();
});
});
@@ -7,6 +7,7 @@ import {
} from '../../../services/productUsage.service'; } from '../../../services/productUsage.service';
import { import {
ArrowUpFromLine, ArrowUpFromLine,
ExternalLink,
Globe, Globe,
ListChecks, ListChecks,
MessageSquare, MessageSquare,
@@ -377,14 +378,29 @@ export default function ProductUsageTab() {
</> </>
)} )}
{data.collector_url && ( {data.collector_url && (
<a <>
className="text-sm text-primary-600 dark:text-primary-400 hover:underline self-center" {/* The public portal itself, not the session-bound link below:
href={`${data.collector_url}/transparency`} it needs neither participation nor a voting session, so an
target="_blank" operator can look at the portal before deciding to join.
rel="noreferrer" Styled as a button so it reads as an action, not a footnote. */}
> <a
{t('productUsage.transparency')} className="btn btn-outline btn-md"
</a> href={data.collector_url}
target="_blank"
rel="noopener noreferrer"
>
{t('productUsage.openUsagePortal')}
<ExternalLink className="ml-2 h-4 w-4" aria-hidden="true" />
</a>
<a
className="text-sm text-primary-600 dark:text-primary-400 hover:underline self-center"
href={`${data.collector_url}/transparency`}
target="_blank"
rel="noreferrer"
>
{t('productUsage.transparency')}
</a>
</>
)} )}
</div> </div>
</Card> </Card>
+2 -1
View File
@@ -435,7 +435,7 @@
"notice": "Optionale Nutzungsberichte zeigen, welche Funktionen für die Community wichtig sind. Die Übermittlung ist aus, bis Sie sich aktiv dafür entscheiden.", "notice": "Optionale Nutzungsberichte zeigen, welche Funktionen für die Community wichtig sind. Die Übermittlung ist aus, bis Sie sich aktiv dafür entscheiden.",
"ignore": "Ignorieren", "ignore": "Ignorieren",
"ignoreHint": "Wenn Sie ignorieren, erscheint dieser Hinweis nicht erneut — Sie können weiterhin unter Einstellungen → Produktnutzung teilnehmen.", "ignoreHint": "Wenn Sie ignorieren, erscheint dieser Hinweis nicht erneut — Sie können weiterhin unter Einstellungen → Produktnutzung teilnehmen.",
"review": "Teilnahme prüfen", "review": "Details ansehen",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"loading": "Teilnahmeeinstellungen werden geladen…", "loading": "Teilnahmeeinstellungen werden geladen…",
"failed": "Der Vorgang konnte nicht abgeschlossen werden. Prüfen Sie den Status und versuchen Sie es erneut.", "failed": "Der Vorgang konnte nicht abgeschlossen werden. Prüfen Sie den Status und versuchen Sie es erneut.",
@@ -458,6 +458,7 @@
"enable": "Produktnutzung aktivieren", "enable": "Produktnutzung aktivieren",
"disable": "Deaktivieren & Daten löschen", "disable": "Deaktivieren & Daten löschen",
"retry": "Erneut versuchen / fälligen Bericht senden", "retry": "Erneut versuchen / fälligen Bericht senden",
"openUsagePortal": "Nutzungsportal öffnen",
"transparency": "Öffentliches Schema & Datenschutzhinweise", "transparency": "Öffentliches Schema & Datenschutzhinweise",
"linkCollector": "Wohin Berichte gesendet werden", "linkCollector": "Wohin Berichte gesendet werden",
"hash": "Ihr vertraulicher Abfrage-Hash", "hash": "Ihr vertraulicher Abfrage-Hash",
+1
View File
@@ -458,6 +458,7 @@
"enable": "Enable product usage", "enable": "Enable product usage",
"disable": "Disable & delete my data", "disable": "Disable & delete my data",
"retry": "Retry / send if due", "retry": "Retry / send if due",
"openUsagePortal": "Open usage portal",
"transparency": "Read the public schema & privacy details", "transparency": "Read the public schema & privacy details",
"linkCollector": "Where reports are sent", "linkCollector": "Where reports are sent",
"hash": "Your private lookup hash", "hash": "Your private lookup hash",