Zrobiłem już większość logiki formularza.

Trzeba jeszcze zrobić tak, aby pytania dla kolejnych hostów pojawiały
się po kolei - tzn dopiero jak dla poprzedniego hosta
hostNeedsQuestions jest false, to pokazujemy następnego. Potem trzeba
popracować nad samym generowaniem treści na podstawie wszystkich tych
odpowiedzi
This commit is contained in:
Kuba Orlik 2022-01-30 21:03:49 +01:00
parent 2c5e4f1005
commit 894391aaa3
9 changed files with 371 additions and 156 deletions

View File

@ -0,0 +1,43 @@
import { EmailTemplate3Config } from './email-template-3';
import hostSettingsRadio from './host-settings-radio';
export default function ConsentProblems({
settings,
host_id,
setConfig,
pronoun,
}: {
host_id: string;
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
settings: EmailTemplate3Config['hosts_settings'][string];
pronoun: 0 | 1 | 2 | 3;
}) {
if (settings.legal_basis_type !== 'consent') {
return '';
}
const p = pronoun;
return (
<div>
{hostSettingsRadio({
host_id,
setConfig,
field: 'consent_problems' as const,
value: settings.consent_problems,
options: {
claims_consent_but_sends_before_consent: /* HTML */ `Strona wysłała
${p == 3 ? 'nasze' : 'moje'} dane do ${host_id} zanim
${['wyraziłem', 'wyraziłam', 'wyraziłom', 'wyraziliśmy'][p]} na to zgodę.`,
claims_consent_but_there_was_no_easy_refuse: /* HTML */ `${[
'Kliknąłem',
'Kliknęłam',
'Kliknęłom',
'Kliknęliśmy',
][p]}
przycisk od wyrażania zgody, ale w okienku o zgodę nie było natychmiastowo
dostępnego przycisku do niewyrażenia zgody jednym kliknięciem.`,
none: 'Żadne z powyższych.',
},
})}
</div>
);
}

View File

@ -0,0 +1,108 @@
import { EmailTemplate3Config } from './email-template-3';
import hostSettingsDropdown from './host-setting-dropdown';
import ConsentProblems from './consent-problems';
import LegitimateInteresProblems from './legitimate-interest-problems';
import { hostNeedsQuestions } from './host-needs-questions';
export function setHostSetting<
P1 extends keyof EmailTemplate3Config['hosts_settings'],
P2 extends keyof EmailTemplate3Config['hosts_settings'][P1]
>(
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>,
[p1, p2]: [P1, P2],
value: EmailTemplate3Config['hosts_settings'][P1][P2]
) {
setConfig((v) => {
console.log(v, {
...v,
hosts_settings: {
...v.hosts_settings,
[p1]: {
...v.hosts_settings[p2],
[p2]: value,
},
},
});
return {
...v,
hosts_settings: {
...v.hosts_settings,
[p1]: {
...v.hosts_settings[p1],
[p2]: value,
},
},
};
});
}
export default function emailHostSettings(
config: EmailTemplate3Config,
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>
) {
if (config.policy_readable == 'null') {
return '';
}
const p = config.pronoun;
return (
<div>
{Object.entries(config.hosts_settings).map(([host_id, settings]) => (
<div key={host_id}>
<h5>
{host_id}, {hostNeedsQuestions(settings).toString()}
</h5>
<p>
Cele przetwarzania danych przez właściciela domeny {host_id}{' '}
{hostSettingsDropdown({
host_id,
setConfig,
settings,
field: 'presence',
value: settings.presence,
options: {
not_mentioned: ['nie są nigdzie na stronie opisane'],
mentioned_in_policy: [
'są opisane w polityce prywatności',
config.policy_readable !== 'yes',
],
mentioned_in_popup: ['są opisane w okienku RODO'],
},
})}
</p>
{!['not_mentioned', 'null'].includes(settings.presence) ? (
<p>
Wskazana przez administratora podstawa prawna dla{' '}
<strong> tego konkretnego celu</strong>{' '}
{hostSettingsDropdown({
host_id,
setConfig,
settings,
field: 'legal_basis_type' as const,
value: settings.legal_basis_type,
options: {
consent: ['to zgoda.'],
legitimate_interest: ['to uzasadniony interes.'],
not_mentioned: ['nie jest wskazana nigdzie na stronie.'],
},
})}
</p>
) : (
''
)}
{!['not_mentioned', 'null'].includes(settings.legal_basis_type) ? (
<div>
{ConsentProblems({ settings, host_id, pronoun: p, setConfig })}
{LegitimateInteresProblems({
settings,
host_id,
setConfig,
})}
</div>
) : (
''
)}
</div>
))}
</div>
);
}

View File

@ -1,39 +1,9 @@
import React from 'react'; import React from 'react';
import { toBase64 } from '../util'; import { toBase64 } from '../util';
import ConsentProblems from './consent-problems';
import emailHostSettings from './email-host-settings';
import { EmailTemplate3Config } from './email-template-3'; import { EmailTemplate3Config } from './email-template-3';
function setHostSetting<
P1 extends keyof EmailTemplate3Config['hosts_settings'],
P2 extends keyof EmailTemplate3Config['hosts_settings'][P1]
>(
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>,
[p1, p2]: [P1, P2],
value: EmailTemplate3Config['hosts_settings'][P1][P2]
) {
setConfig((v) => {
console.log(v, {
...v,
hosts_settings: {
...v.hosts_settings,
[p1]: {
...v.hosts_settings[p2],
[p2]: value,
},
},
});
return {
...v,
hosts_settings: {
...v.hosts_settings,
[p1]: {
...v.hosts_settings[p1],
[p2]: value,
},
},
};
});
}
export default function EmailTemplate3Controls({ export default function EmailTemplate3Controls({
config, config,
setConfig, setConfig,
@ -92,82 +62,7 @@ export default function EmailTemplate3Controls({
</option> </option>
</select> </select>
</div> </div>
{config.policy_readable !== 'null' ? ( {emailHostSettings(config, setConfig)}
<div>
{Object.entries(config.hosts_settings).map(([id, settings]) => (
<div key={id}>
<h5>{id}</h5>
<p>
Cele przetwarzania danych przez właściciela domeny {id}{' '}
<select
value={settings.presence}
onChange={(e) =>
setHostSetting(
setConfig,
[id, 'presence'],
e.target
.value as EmailTemplate3Config['hosts_settings'][string]['presence']
)
}
>
<option value="null" disabled>
wybierz opcję
</option>
<option value="not_mentioned">
nie nigdzie na stronie opisane{' '}
</option>
<option
value="mentioned_in_policy"
disabled={config.policy_readable !== 'yes'}
>
opisane w polityce prywatności{' '}
</option>
<option value="mentioned_in_popup">
opisane w okienku RODO{' '}
</option>
</select>
</p>
{!['not_mentioned', 'null'].includes(settings.presence) ? (
<p>
Wskazana przez administratora podstawa prawna dla{' '}
<strong> tego konkretnego celu</strong>{' '}
<select
value={settings.legal_basis_type}
onChange={(e) =>
setHostSetting(
setConfig,
[id, 'legal_basis_type'],
e.target
.value as EmailTemplate3Config['hosts_settings'][string]['legal_basis_type']
)
}
>
<option value="null" disabled>
wybierz opcję
</option>
<option value="consent">to zgoda.</option>
<option value="legitimate_interest">
to uzasadniony interes.
</option>
<option value="not_mentioned">
nie jest wskazana nigdzie na stronie.
</option>
</select>
</p>
) : (
''
)}
{!['not_mentioned', 'null'].includes(settings.legal_basis_type) ? (
<div>dodatkowe pytania</div>
) : (
''
)}
</div>
))}
</div>
) : (
''
)}
<div> <div>
<label htmlFor="poup_type">Typ okienka o RODO: </label> <label htmlFor="poup_type">Typ okienka o RODO: </label>
<select <select
@ -207,26 +102,6 @@ export default function EmailTemplate3Controls({
) : ( ) : (
'' ''
)} )}
{config.popup_type === 'consent' ? (
<div>
<label htmlFor="acceptAllName">
Tekst na przycisku do zatwierdzania wszystkich zgód:
</label>
<input
{...{
type: 'text',
value: config.popup_accept_all_text,
onChange: (e) =>
setConfig((v) => ({
...v,
popup_accept_all_text: e.target.value,
})),
}}
/>
</div>
) : (
''
)}
<div> <div>
<label htmlFor="popup_action">Czy coś klikn*ł*m w informacjach o RODO?</label> <label htmlFor="popup_action">Czy coś klikn*ł*m w informacjach o RODO?</label>
<select <select

View File

@ -15,11 +15,14 @@ export type EmailTemplate3Config = {
{ {
presence: 'null' | 'not_mentioned' | 'mentioned_in_popup' | 'mentioned_in_policy'; presence: 'null' | 'not_mentioned' | 'mentioned_in_popup' | 'mentioned_in_policy';
legal_basis_type: 'null' | 'consent' | 'legitimate_interest' | 'not_mentioned'; legal_basis_type: 'null' | 'consent' | 'legitimate_interest' | 'not_mentioned';
problems: { consent_problems?:
claims_consent_but_sends_before_consent: boolean; | 'claims_consent_but_sends_before_consent'
claims_consent_but_there_was_no_easy_refuse: boolean; | 'claims_consent_but_there_was_no_easy_refuse'
claims_legitimate_interest_but_its_not_legitimate: boolean; | 'none'
}; | 'null';
legitimate_interest_activity_specified: 'null' | 'no' | 'precise' | 'vague';
legitimate_interest_activity_description: string;
} }
>; >;
popup_type: 'none' | 'passive_cookie_banner' | 'consent'; popup_type: 'none' | 'passive_cookie_banner' | 'consent';
@ -51,11 +54,9 @@ export default function EmailTemplate3({
presence: 'null', presence: 'null',
legal_basis_presence: 'null', legal_basis_presence: 'null',
legal_basis_type: 'null', legal_basis_type: 'null',
problems: { consent_problems: 'null',
claims_consent_but_sends_before_consent: false, legitimate_interest_activity_specified: 'null',
claims_consent_but_there_was_no_easy_refuse: false, legitimate_interest_activity_description: '',
claims_legitimate_interest_but_its_not_legitimate: false,
},
}, },
]) ])
), ),
@ -112,7 +113,7 @@ export default function EmailTemplate3({
szansę przeczytać Państwa politykę prywatności i w jakikolwiek czynny i szansę przeczytać Państwa politykę prywatności i w jakikolwiek czynny i
jednoznaczny sposób wyrazić zgodę na takie przetwarzanie moich danych osobowych. jednoznaczny sposób wyrazić zgodę na takie przetwarzanie moich danych osobowych.
</p> </p>
{config.policy_readable !== 'yes' ? ( {!['yes', 'null'].includes(config.policy_readable) ? (
<p> <p>
{['Chciałem', 'Chciałam', 'Chciałom', 'Chcieliśmy'][p]} przeczytać Państwa {['Chciałem', 'Chciałam', 'Chciałom', 'Chcieliśmy'][p]} przeczytać Państwa
politykę prywatności przed akceptacją, ale{' '} politykę prywatności przed akceptacją, ale{' '}
@ -137,9 +138,8 @@ export default function EmailTemplate3({
stronie. Z tego powodu zwracam{p == 3 ? 'y' : ''} się do Państwa z pytaniem: stronie. Z tego powodu zwracam{p == 3 ? 'y' : ''} się do Państwa z pytaniem:
jakie były podstawy prawne takiego ujawnienia{' '} jakie były podstawy prawne takiego ujawnienia{' '}
{['moich', 'moich', 'moich', 'naszych'][p]} danych osobowych wyżej wymienionym {['moich', 'moich', 'moich', 'naszych'][p]} danych osobowych wyżej wymienionym
podmiotom? Uprzejmie podmiotom? Uprzejmie {['proszę', 'proszę', 'proszę', 'prosimy'][p]} o wskazanie
{['proszę', 'proszę', 'proszę', 'prosimy'][p]} o wskazanie podstawy prawnej dla podstawy prawnej dla każdego z tych podmiotów z osobna.
każdego z tych podmiotów z osobna.
</p> </p>
</article> </article>
</div> </div>

View File

@ -0,0 +1,32 @@
import { EmailTemplate3Config } from './email-template-3';
export function hostNeedsQuestions({
presence,
legal_basis_type,
consent_problems,
legitimate_interest_activity_description,
legitimate_interest_activity_specified,
}: EmailTemplate3Config['hosts_settings'][string]) {
if (presence == 'not_mentioned') {
return false;
}
if (legal_basis_type == 'not_mentioned') {
return false;
}
if (legal_basis_type == 'consent' && consent_problems !== 'null') {
return false;
}
if (
legitimate_interest_activity_specified !== 'null' &&
legitimate_interest_activity_specified !== 'vague'
) {
return false;
}
if (
legal_basis_type == 'legitimate_interest' &&
legitimate_interest_activity_description != ''
) {
return false;
}
return true;
}

View File

@ -0,0 +1,46 @@
import { setHostSetting } from './email-host-settings';
import { EmailTemplate3Config } from './email-template-3';
export default function hostSettingsDropdown<
Field extends keyof EmailTemplate3Config['hosts_settings'][string]
>({
host_id,
setConfig,
value,
field,
options,
}: {
host_id: string;
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
settings: EmailTemplate3Config['hosts_settings'][string];
field: Field;
value: string;
options: Record<
Exclude<EmailTemplate3Config['hosts_settings'][string][Field], 'null'>,
[string, boolean?]
>;
}) {
return (
<select
value={value}
onChange={(e) =>
setHostSetting(
setConfig,
[host_id, field],
e.target.value as EmailTemplate3Config['hosts_settings'][string][typeof field]
)
}
>
<option value="null" disabled>
wybierz opcję
</option>
{Object.entries(options).map(
([value, [display, disabled]]: [string, [string, boolean]]) => (
<option value={value} disabled={disabled}>
{display}
</option>
)
)}
</select>
);
}

View File

@ -0,0 +1,47 @@
import { normalizeForClassname } from '../util';
import { EmailTemplate3Config } from './email-template-3';
export default function hostSettingsRadio<
Field extends keyof EmailTemplate3Config['hosts_settings'][string]
>({
options,
host_id,
field,
setConfig,
value,
}: {
host_id: string;
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
field: Field;
options: Record<Exclude<EmailTemplate3Config['hosts_settings'][string][Field], 'null'>, string>;
value: EmailTemplate3Config['hosts_settings'][string][Field];
}) {
return (
<div>
{Object.entries(options).map(([option_value, display]) => (
<div>
<input
type="radio"
name={normalizeForClassname(host_id + '_consent_problems')}
value={option_value}
checked={value == option_value}
id={normalizeForClassname(option_value + host_id)}
onChange={(e) => {
setConfig((v) => ({
...v,
hosts_settings: {
...v.hosts_settings,
[host_id]: {
...v.hosts_settings[host_id],
[field]: e.target.value,
},
},
}));
}}
/>
<label htmlFor={normalizeForClassname(option_value + host_id)}>{display}</label>
</div>
))}
</div>
);
}

View File

@ -0,0 +1,66 @@
import React from 'react';
import { setHostSetting } from './email-host-settings';
import { EmailTemplate3Config } from './email-template-3';
import hostSettingsDropdown from './host-setting-dropdown';
export default function LegitimateInteresProblems({
settings,
host_id,
pronoun,
setConfig,
}: {
host_id: string;
settings: EmailTemplate3Config['hosts_settings'][string];
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
}) {
if (settings.legal_basis_type !== 'legitimate_interest') {
return '';
}
const [description, setDescription] = React.useState('marketing');
return (
<div>
<div>
Czy administrator strony opisał szczegółowo, na czym polega uzasadniony interes w
kontekście tego celu?
{hostSettingsDropdown({
settings,
host_id,
setConfig,
field: 'legitimate_interest_activity_specified' as const,
value: settings.legitimate_interest_activity_specified,
options: {
precise: [
'Tak, wskazuje jasno na bieżące działania lub korzyści wynikające z takiego przetwarzania danych.',
],
vague: ['Wskazuje tylko ogólnie, jak np. „marketing” czy „statystyki”.'],
no: ['Nie. Nie wiadomo, na czym ten uzasadniony interes polega.'],
},
})}
</div>
{settings.legitimate_interest_activity_specified === 'vague' ? (
<div>
Jak administrator opisał to, na czym polega uzasadniony interes w kontekście{' '}
{host_id}?{' '}
<input
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<button
onClick={(e) =>
setHostSetting(
setConfig,
[host_id, 'legitimate_interest_activity_description'],
description
)
}
>
zatwierdź
</button>
</div>
) : (
''
)}
</div>
);
}

26
util.ts
View File

@ -87,17 +87,13 @@ export function parseToObject(str: unknown): Record<string | symbol, unknown> {
result = JSON.parse(str); result = JSON.parse(str);
} else if (typeof str == 'object') { } else if (typeof str == 'object') {
result = str as Record<string | symbol, unknown>; result = str as Record<string | symbol, unknown>;
original_string = original_string = (result[Symbol.for('originalString')] as string) || JSON.stringify(str);
(result[Symbol.for('originalString')] as string) ||
JSON.stringify(str);
} }
result[Symbol.for('originalString')] = original_string; result[Symbol.for('originalString')] = original_string;
return result; return result;
} }
export function isJSONObject( export function isJSONObject(str: unknown): str is Record<string, unknown> | string | number {
str: unknown
): str is Record<string, unknown> | string | number {
try { try {
const firstChar = JSON.stringify(parseToObject(str))[0]; const firstChar = JSON.stringify(parseToObject(str))[0];
return ['{', '['].includes(firstChar); return ['{', '['].includes(firstChar);
@ -137,9 +133,10 @@ export function reduceConcat<T>(a: T[], b: T[]): T[] {
export function getDate() { export function getDate() {
const d = new Date(); const d = new Date();
return `${d.getFullYear()}-${(d.getMonth() + 1) return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d
.getDate()
.toString() .toString()
.padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`; .padStart(2, '0')}`;
} }
export function toBase64(file: File): Promise<string> { export function toBase64(file: File): Promise<string> {
@ -193,8 +190,7 @@ export function isBase64JSON(s: unknown): s is string {
export function flattenObject( export function flattenObject(
obj: unknown, obj: unknown,
parser: (to_parse: unknown) => string | Record<string, unknown> = (id) => parser: (to_parse: unknown) => string | Record<string, unknown> = (id) => id.toString(),
id.toString(),
key = '', key = '',
ret = [] as [string, string][], ret = [] as [string, string][],
parsed = false parsed = false
@ -224,8 +220,7 @@ export function flattenObject(
export function flattenObjectEntries( export function flattenObjectEntries(
entries: [string, unknown][], entries: [string, unknown][],
parser: (to_parse: unknown) => string | Record<string, unknown> = (id) => parser: (to_parse: unknown) => string | Record<string, unknown> = (id) => id.toString()
id.toString()
): [string, string][] { ): [string, string][] {
return flattenObject(Object.fromEntries(entries), parser); return flattenObject(Object.fromEntries(entries), parser);
} }
@ -236,8 +231,7 @@ export function maskString(
max_chars_total: number max_chars_total: number
): string { ): string {
const amount_of_chars_to_cut = const amount_of_chars_to_cut =
str.length - str.length - Math.min(str.length * max_fraction_remaining, max_chars_total);
Math.min(str.length * max_fraction_remaining, max_chars_total);
if (amount_of_chars_to_cut == 0) { if (amount_of_chars_to_cut == 0) {
return str; return str;
} }
@ -255,3 +249,7 @@ export function safeDecodeURIComponent(s: string) {
return s; return s;
} }
} }
export function normalizeForClassname(string: string) {
return string.replace(/[^a-z0-9]/gi, '-');
}