wagtail-enap-designsystem 1.2.1.125__py3-none-any.whl → 1.2.1.127__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- enap_designsystem/blocks/__init__.py +2 -0
- enap_designsystem/blocks/html_blocks.py +63 -1
- enap_designsystem/migrations/0387_alter_areaaluno_body_alter_enapcomponentes_body_and_more.py +51190 -0
- enap_designsystem/migrations/0388_sistemavotacaopage_configuracoes_votacao.py +132 -0
- enap_designsystem/migrations/0389_alter_sistemavotacaopage_configuracoes_votacao.py +81 -0
- enap_designsystem/models.py +12 -0
- enap_designsystem/templates/enap_designsystem/blocks/page/pagenoticias_block.html +2 -1
- enap_designsystem/templates/enap_designsystem/blocks/recaptcha.html +338 -0
- enap_designsystem/templates/enap_designsystem/sistema_votacao_page.html +6 -0
- enap_designsystem/views.py +2 -1
- {wagtail_enap_designsystem-1.2.1.125.dist-info → wagtail_enap_designsystem-1.2.1.127.dist-info}/METADATA +1 -1
- {wagtail_enap_designsystem-1.2.1.125.dist-info → wagtail_enap_designsystem-1.2.1.127.dist-info}/RECORD +15 -11
- {wagtail_enap_designsystem-1.2.1.125.dist-info → wagtail_enap_designsystem-1.2.1.127.dist-info}/WHEEL +0 -0
- {wagtail_enap_designsystem-1.2.1.125.dist-info → wagtail_enap_designsystem-1.2.1.127.dist-info}/licenses/LICENSE +0 -0
- {wagtail_enap_designsystem-1.2.1.125.dist-info → wagtail_enap_designsystem-1.2.1.127.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Generated by Django 5.1.6 on 2025-09-04 22:04
|
|
2
|
+
|
|
3
|
+
import wagtail.fields
|
|
4
|
+
from django.db import migrations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
(
|
|
11
|
+
"enap_designsystem",
|
|
12
|
+
"0387_alter_areaaluno_body_alter_enapcomponentes_body_and_more",
|
|
13
|
+
),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.AddField(
|
|
18
|
+
model_name="sistemavotacaopage",
|
|
19
|
+
name="configuracoes_votacao",
|
|
20
|
+
field=wagtail.fields.StreamField(
|
|
21
|
+
[
|
|
22
|
+
("recaptcha", 5),
|
|
23
|
+
("texto_instrucoes", 6),
|
|
24
|
+
("aviso_importante", 10),
|
|
25
|
+
("separador", 11),
|
|
26
|
+
],
|
|
27
|
+
blank=True,
|
|
28
|
+
block_lookup={
|
|
29
|
+
0: (
|
|
30
|
+
"wagtail.blocks.ChoiceBlock",
|
|
31
|
+
[],
|
|
32
|
+
{
|
|
33
|
+
"choices": [
|
|
34
|
+
("v2", "reCAPTCHA v2 (checkbox visível)"),
|
|
35
|
+
("v3", "reCAPTCHA v3 (invisível)"),
|
|
36
|
+
("v2_invisible", "reCAPTCHA v2 invisível"),
|
|
37
|
+
],
|
|
38
|
+
"help_text": "Tipo de reCAPTCHA",
|
|
39
|
+
},
|
|
40
|
+
),
|
|
41
|
+
1: (
|
|
42
|
+
"wagtail.blocks.ChoiceBlock",
|
|
43
|
+
[],
|
|
44
|
+
{
|
|
45
|
+
"choices": [("light", "Claro"), ("dark", "Escuro")],
|
|
46
|
+
"help_text": "Tema visual (apenas v2)",
|
|
47
|
+
},
|
|
48
|
+
),
|
|
49
|
+
2: (
|
|
50
|
+
"wagtail.blocks.ChoiceBlock",
|
|
51
|
+
[],
|
|
52
|
+
{
|
|
53
|
+
"choices": [("normal", "Normal"), ("compact", "Compacto")],
|
|
54
|
+
"help_text": "Tamanho do widget (apenas v2)",
|
|
55
|
+
},
|
|
56
|
+
),
|
|
57
|
+
3: (
|
|
58
|
+
"wagtail.blocks.CharBlock",
|
|
59
|
+
(),
|
|
60
|
+
{"help_text": "Classes CSS adicionais", "required": False},
|
|
61
|
+
),
|
|
62
|
+
4: (
|
|
63
|
+
"wagtail.blocks.CharBlock",
|
|
64
|
+
(),
|
|
65
|
+
{
|
|
66
|
+
"default": "submit",
|
|
67
|
+
"help_text": "Ação para reCAPTCHA v3 (ex: login, submit, homepage)",
|
|
68
|
+
"required": False,
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
5: (
|
|
72
|
+
"wagtail.blocks.StructBlock",
|
|
73
|
+
[
|
|
74
|
+
[
|
|
75
|
+
("tipo", 0),
|
|
76
|
+
("tema", 1),
|
|
77
|
+
("tamanho", 2),
|
|
78
|
+
("css_classes", 3),
|
|
79
|
+
("acao_v3", 4),
|
|
80
|
+
]
|
|
81
|
+
],
|
|
82
|
+
{},
|
|
83
|
+
),
|
|
84
|
+
6: (
|
|
85
|
+
"wagtail.blocks.RichTextBlock",
|
|
86
|
+
(),
|
|
87
|
+
{
|
|
88
|
+
"help_text": "Instruções sobre como votar",
|
|
89
|
+
"icon": "doc-full",
|
|
90
|
+
"label": "Texto de Instruções",
|
|
91
|
+
},
|
|
92
|
+
),
|
|
93
|
+
7: (
|
|
94
|
+
"wagtail.blocks.CharBlock",
|
|
95
|
+
(),
|
|
96
|
+
{"label": "Título do Aviso", "max_length": 100},
|
|
97
|
+
),
|
|
98
|
+
8: ("wagtail.blocks.RichTextBlock", (), {"label": "Mensagem"}),
|
|
99
|
+
9: (
|
|
100
|
+
"wagtail.blocks.ChoiceBlock",
|
|
101
|
+
[],
|
|
102
|
+
{
|
|
103
|
+
"choices": [
|
|
104
|
+
("info", "Informação"),
|
|
105
|
+
("warning", "Aviso"),
|
|
106
|
+
("success", "Sucesso"),
|
|
107
|
+
("danger", "Perigo"),
|
|
108
|
+
],
|
|
109
|
+
"label": "Tipo de Aviso",
|
|
110
|
+
},
|
|
111
|
+
),
|
|
112
|
+
10: (
|
|
113
|
+
"wagtail.blocks.StructBlock",
|
|
114
|
+
[[("titulo", 7), ("mensagem", 8), ("tipo", 9)]],
|
|
115
|
+
{"icon": "warning", "label": "Aviso/Alerta"},
|
|
116
|
+
),
|
|
117
|
+
11: (
|
|
118
|
+
"wagtail.blocks.static_block.StaticBlock",
|
|
119
|
+
(),
|
|
120
|
+
{
|
|
121
|
+
"admin_text": "Separador visual entre seções",
|
|
122
|
+
"icon": "horizontalrule",
|
|
123
|
+
"label": "Separador",
|
|
124
|
+
"template": "blocks/separador.html",
|
|
125
|
+
},
|
|
126
|
+
),
|
|
127
|
+
},
|
|
128
|
+
help_text="Adicione reCAPTCHA, instruções e outros elementos para a página de votação",
|
|
129
|
+
verbose_name="Configurações e Elementos da Votação",
|
|
130
|
+
),
|
|
131
|
+
),
|
|
132
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Generated by Django 5.1.6 on 2025-09-04 22:13
|
|
2
|
+
|
|
3
|
+
import wagtail.fields
|
|
4
|
+
from django.db import migrations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("enap_designsystem", "0388_sistemavotacaopage_configuracoes_votacao"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name="sistemavotacaopage",
|
|
16
|
+
name="configuracoes_votacao",
|
|
17
|
+
field=wagtail.fields.StreamField(
|
|
18
|
+
[("recaptcha", 5)],
|
|
19
|
+
blank=True,
|
|
20
|
+
block_lookup={
|
|
21
|
+
0: (
|
|
22
|
+
"wagtail.blocks.ChoiceBlock",
|
|
23
|
+
[],
|
|
24
|
+
{
|
|
25
|
+
"choices": [
|
|
26
|
+
("v2", "reCAPTCHA v2 (checkbox visível)"),
|
|
27
|
+
("v3", "reCAPTCHA v3 (invisível)"),
|
|
28
|
+
("v2_invisible", "reCAPTCHA v2 invisível"),
|
|
29
|
+
],
|
|
30
|
+
"help_text": "Tipo de reCAPTCHA",
|
|
31
|
+
},
|
|
32
|
+
),
|
|
33
|
+
1: (
|
|
34
|
+
"wagtail.blocks.ChoiceBlock",
|
|
35
|
+
[],
|
|
36
|
+
{
|
|
37
|
+
"choices": [("light", "Claro"), ("dark", "Escuro")],
|
|
38
|
+
"help_text": "Tema visual (apenas v2)",
|
|
39
|
+
},
|
|
40
|
+
),
|
|
41
|
+
2: (
|
|
42
|
+
"wagtail.blocks.ChoiceBlock",
|
|
43
|
+
[],
|
|
44
|
+
{
|
|
45
|
+
"choices": [("normal", "Normal"), ("compact", "Compacto")],
|
|
46
|
+
"help_text": "Tamanho do widget (apenas v2)",
|
|
47
|
+
},
|
|
48
|
+
),
|
|
49
|
+
3: (
|
|
50
|
+
"wagtail.blocks.CharBlock",
|
|
51
|
+
(),
|
|
52
|
+
{"help_text": "Classes CSS adicionais", "required": False},
|
|
53
|
+
),
|
|
54
|
+
4: (
|
|
55
|
+
"wagtail.blocks.CharBlock",
|
|
56
|
+
(),
|
|
57
|
+
{
|
|
58
|
+
"default": "submit",
|
|
59
|
+
"help_text": "Ação para reCAPTCHA v3 (ex: login, submit, homepage)",
|
|
60
|
+
"required": False,
|
|
61
|
+
},
|
|
62
|
+
),
|
|
63
|
+
5: (
|
|
64
|
+
"wagtail.blocks.StructBlock",
|
|
65
|
+
[
|
|
66
|
+
[
|
|
67
|
+
("tipo", 0),
|
|
68
|
+
("tema", 1),
|
|
69
|
+
("tamanho", 2),
|
|
70
|
+
("css_classes", 3),
|
|
71
|
+
("acao_v3", 4),
|
|
72
|
+
]
|
|
73
|
+
],
|
|
74
|
+
{},
|
|
75
|
+
),
|
|
76
|
+
},
|
|
77
|
+
help_text="Adicione reCAPTCHA para a página de votação",
|
|
78
|
+
verbose_name="Configurações e Elementos da Votação",
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
]
|
enap_designsystem/models.py
CHANGED
|
@@ -19,6 +19,7 @@ from wagtail.blocks import URLBlock
|
|
|
19
19
|
from wagtail.search import index
|
|
20
20
|
import requests
|
|
21
21
|
import os
|
|
22
|
+
from .blocks.html_blocks import RecaptchaBlock
|
|
22
23
|
from django.db.models.signals import pre_delete
|
|
23
24
|
from django.dispatch import receiver
|
|
24
25
|
import shutil
|
|
@@ -4858,6 +4859,15 @@ class SistemaVotacaoPage(Page):
|
|
|
4858
4859
|
help_text="Data/hora de encerramento da votação (opcional)"
|
|
4859
4860
|
)
|
|
4860
4861
|
|
|
4862
|
+
configuracoes_votacao = StreamField([
|
|
4863
|
+
('recaptcha', RecaptchaBlock()),
|
|
4864
|
+
],
|
|
4865
|
+
blank=True,
|
|
4866
|
+
use_json_field=True,
|
|
4867
|
+
verbose_name="Configurações e Elementos da Votação",
|
|
4868
|
+
help_text="Adicione reCAPTCHA para a página de votação"
|
|
4869
|
+
)
|
|
4870
|
+
|
|
4861
4871
|
footer = models.ForeignKey(
|
|
4862
4872
|
"EnapFooterSnippet",
|
|
4863
4873
|
null=True,
|
|
@@ -4876,6 +4886,8 @@ class SistemaVotacaoPage(Page):
|
|
|
4876
4886
|
FieldPanel('navbar'),
|
|
4877
4887
|
FieldPanel('footer'),
|
|
4878
4888
|
], heading="Conteúdo do Header"),
|
|
4889
|
+
|
|
4890
|
+
FieldPanel('configuracoes_votacao'),
|
|
4879
4891
|
|
|
4880
4892
|
MultiFieldPanel([
|
|
4881
4893
|
FieldPanel('mostrar_progresso'),
|
|
@@ -253,6 +253,7 @@
|
|
|
253
253
|
grid-template-columns: 1fr 1fr;
|
|
254
254
|
gap: 30px;
|
|
255
255
|
margin-bottom: 40px;
|
|
256
|
+
max-height: 600px;
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
.news-grid-main {
|
|
@@ -336,7 +337,7 @@
|
|
|
336
337
|
display: flex;
|
|
337
338
|
flex-direction: column;
|
|
338
339
|
gap: 20px;
|
|
339
|
-
max-height:
|
|
340
|
+
max-height: 600px;
|
|
340
341
|
}
|
|
341
342
|
|
|
342
343
|
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
<!-- templates/blocks/recaptcha.html -->
|
|
2
|
+
<div class="recaptcha-container {{ value.css_classes }}" data-recaptcha-id="{{ block.id }}">
|
|
3
|
+
|
|
4
|
+
{% if has_recaptcha_keys %}
|
|
5
|
+
<!-- reCAPTCHA v2 (checkbox + desafio) -->
|
|
6
|
+
<div class="g-recaptcha"
|
|
7
|
+
data-sitekey="{{ recaptcha_public_key }}"
|
|
8
|
+
data-theme="{{ value.tema }}"
|
|
9
|
+
data-size="{{ value.tamanho }}"
|
|
10
|
+
data-callback="onRecaptchaSuccess{{ block.id }}"
|
|
11
|
+
data-expired-callback="onRecaptchaExpired{{ block.id }}"
|
|
12
|
+
data-error-callback="onRecaptchaError{{ block.id }}"
|
|
13
|
+
id="recaptcha-{{ block.id }}">
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- Status da verificação -->
|
|
17
|
+
<div id="status-{{ block.id }}" class="recaptcha-status mt-3" style="display: none;">
|
|
18
|
+
<div class="alert alert-success">
|
|
19
|
+
<i class="fas fa-check-circle"></i>
|
|
20
|
+
<strong>Verificação completa!</strong>
|
|
21
|
+
Você passou no teste reCAPTCHA.
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- Status de erro -->
|
|
26
|
+
<div id="error-{{ block.id }}" class="recaptcha-error mt-3" style="display: none;">
|
|
27
|
+
<div class="alert alert-danger">
|
|
28
|
+
<i class="fas fa-exclamation-triangle"></i>
|
|
29
|
+
<strong>Erro no reCAPTCHA!</strong>
|
|
30
|
+
Tente novamente.
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
{% else %}
|
|
35
|
+
<div class="alert alert-warning">
|
|
36
|
+
<i class="fas fa-exclamation-triangle"></i>
|
|
37
|
+
<strong>reCAPTCHA não configurado!</strong>
|
|
38
|
+
<br><small>Configure RECAPTCHA_PUBLIC_KEY no settings.py</small>
|
|
39
|
+
</div>
|
|
40
|
+
{% endif %}
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<script>
|
|
44
|
+
// Callback quando o usuário completa o reCAPTCHA
|
|
45
|
+
window.onRecaptchaSuccess{{ block.id }} = function(response) {
|
|
46
|
+
console.log('✅ reCAPTCHA completado com sucesso!');
|
|
47
|
+
console.log('Token recebido:', response);
|
|
48
|
+
|
|
49
|
+
// Mostrar mensagem de sucesso
|
|
50
|
+
const statusDiv = document.getElementById('status-{{ block.id }}');
|
|
51
|
+
const errorDiv = document.getElementById('error-{{ block.id }}');
|
|
52
|
+
|
|
53
|
+
if (statusDiv) statusDiv.style.display = 'block';
|
|
54
|
+
if (errorDiv) errorDiv.style.display = 'none';
|
|
55
|
+
|
|
56
|
+
// Marcar como verificado
|
|
57
|
+
const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
|
|
58
|
+
container.setAttribute('data-verified', 'true');
|
|
59
|
+
container.setAttribute('data-response', response);
|
|
60
|
+
container.classList.add('recaptcha-verified');
|
|
61
|
+
container.classList.remove('recaptcha-error');
|
|
62
|
+
|
|
63
|
+
// Disparar evento personalizado para integração
|
|
64
|
+
document.dispatchEvent(new CustomEvent('recaptchaCompleted', {
|
|
65
|
+
detail: {
|
|
66
|
+
blockId: '{{ block.id }}',
|
|
67
|
+
response: response,
|
|
68
|
+
success: true
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
// Habilitar botões de submit se existirem
|
|
73
|
+
const submitButtons = document.querySelectorAll('button[type="submit"], input[type="submit"]');
|
|
74
|
+
submitButtons.forEach(btn => {
|
|
75
|
+
btn.disabled = false;
|
|
76
|
+
btn.classList.remove('disabled');
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Callback quando expira
|
|
81
|
+
window.onRecaptchaExpired{{ block.id }} = function() {
|
|
82
|
+
console.log('⚠️ reCAPTCHA expirado');
|
|
83
|
+
|
|
84
|
+
const statusDiv = document.getElementById('status-{{ block.id }}');
|
|
85
|
+
const errorDiv = document.getElementById('error-{{ block.id }}');
|
|
86
|
+
|
|
87
|
+
if (statusDiv) statusDiv.style.display = 'none';
|
|
88
|
+
if (errorDiv) {
|
|
89
|
+
errorDiv.querySelector('.alert').innerHTML = `
|
|
90
|
+
<i class="fas fa-clock"></i>
|
|
91
|
+
<strong>reCAPTCHA expirado!</strong>
|
|
92
|
+
Clique novamente para verificar.
|
|
93
|
+
`;
|
|
94
|
+
errorDiv.style.display = 'block';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
|
|
98
|
+
container.setAttribute('data-verified', 'false');
|
|
99
|
+
container.classList.remove('recaptcha-verified');
|
|
100
|
+
container.classList.add('recaptcha-error');
|
|
101
|
+
|
|
102
|
+
// Desabilitar botões de submit
|
|
103
|
+
const submitButtons = document.querySelectorAll('button[type="submit"], input[type="submit"]');
|
|
104
|
+
submitButtons.forEach(btn => {
|
|
105
|
+
btn.disabled = true;
|
|
106
|
+
btn.classList.add('disabled');
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Callback de erro
|
|
111
|
+
window.onRecaptchaError{{ block.id }} = function() {
|
|
112
|
+
console.log('❌ Erro no reCAPTCHA');
|
|
113
|
+
|
|
114
|
+
const statusDiv = document.getElementById('status-{{ block.id }}');
|
|
115
|
+
const errorDiv = document.getElementById('error-{{ block.id }}');
|
|
116
|
+
|
|
117
|
+
if (statusDiv) statusDiv.style.display = 'none';
|
|
118
|
+
if (errorDiv) {
|
|
119
|
+
errorDiv.querySelector('.alert').innerHTML = `
|
|
120
|
+
<i class="fas fa-exclamation-triangle"></i>
|
|
121
|
+
<strong>Erro no reCAPTCHA!</strong>
|
|
122
|
+
Verifique sua conexão e tente novamente.
|
|
123
|
+
`;
|
|
124
|
+
errorDiv.style.display = 'block';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
|
|
128
|
+
container.setAttribute('data-verified', 'false');
|
|
129
|
+
container.classList.remove('recaptcha-verified');
|
|
130
|
+
container.classList.add('recaptcha-error');
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Utilitários públicos para JavaScript
|
|
134
|
+
window.recaptchaUtils{{ block.id }} = {
|
|
135
|
+
isVerified: function() {
|
|
136
|
+
const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
|
|
137
|
+
return container && container.getAttribute('data-verified') === 'true';
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
getResponse: function() {
|
|
141
|
+
const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
|
|
142
|
+
return container ? (container.getAttribute('data-response') || '') : '';
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
reset: function() {
|
|
146
|
+
if (typeof grecaptcha !== 'undefined') {
|
|
147
|
+
try {
|
|
148
|
+
grecaptcha.reset();
|
|
149
|
+
console.log('🔄 reCAPTCHA resetado');
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.log('⚠️ Erro ao resetar reCAPTCHA:', e);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
|
|
156
|
+
if (container) {
|
|
157
|
+
container.setAttribute('data-verified', 'false');
|
|
158
|
+
container.classList.remove('recaptcha-verified', 'recaptcha-error');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const statusDiv = document.getElementById('status-{{ block.id }}');
|
|
162
|
+
const errorDiv = document.getElementById('error-{{ block.id }}');
|
|
163
|
+
|
|
164
|
+
if (statusDiv) statusDiv.style.display = 'none';
|
|
165
|
+
if (errorDiv) errorDiv.style.display = 'none';
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
execute: function() {
|
|
169
|
+
if (typeof grecaptcha !== 'undefined') {
|
|
170
|
+
try {
|
|
171
|
+
grecaptcha.execute();
|
|
172
|
+
console.log('▶️ reCAPTCHA executado');
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.log('⚠️ Erro ao executar reCAPTCHA:', e);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Carregar script do Google reCAPTCHA (apenas uma vez)
|
|
181
|
+
if (!document.querySelector('script[src*="recaptcha/api.js"]') && {{ has_recaptcha_keys|yesno:"true,false" }}) {
|
|
182
|
+
const script = document.createElement('script');
|
|
183
|
+
script.src = 'https://www.google.com/recaptcha/api.js';
|
|
184
|
+
script.async = true;
|
|
185
|
+
script.defer = true;
|
|
186
|
+
|
|
187
|
+
script.onload = function() {
|
|
188
|
+
console.log('📡 reCAPTCHA carregado com sucesso!');
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
script.onerror = function() {
|
|
192
|
+
console.log('❌ Erro ao carregar reCAPTCHA do Google');
|
|
193
|
+
|
|
194
|
+
const errorDiv = document.getElementById('error-{{ block.id }}');
|
|
195
|
+
if (errorDiv) {
|
|
196
|
+
errorDiv.querySelector('.alert').innerHTML = `
|
|
197
|
+
<i class="fas fa-wifi"></i>
|
|
198
|
+
<strong>Erro de conexão!</strong>
|
|
199
|
+
Não foi possível carregar o reCAPTCHA.
|
|
200
|
+
`;
|
|
201
|
+
errorDiv.style.display = 'block';
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
document.head.appendChild(script);
|
|
206
|
+
console.log('📡 Carregando reCAPTCHA do Google...');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Event listener global para debug
|
|
210
|
+
document.addEventListener('recaptchaCompleted', function(e) {
|
|
211
|
+
console.log('🎉 Evento reCAPTCHA global:', e.detail);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Debug: informações da configuração
|
|
215
|
+
console.log('🔍 reCAPTCHA Config:');
|
|
216
|
+
console.log('- Block ID:', '{{ block.id }}');
|
|
217
|
+
console.log('- Public Key:', '{{ recaptcha_public_key|truncatechars:20 }}...');
|
|
218
|
+
console.log('- Has Keys:', {{ has_recaptcha_keys|yesno:"true,false" }});
|
|
219
|
+
console.log('- Tema:', '{{ value.tema }}');
|
|
220
|
+
console.log('- Tamanho:', '{{ value.tamanho }}');
|
|
221
|
+
</script>
|
|
222
|
+
|
|
223
|
+
<style>
|
|
224
|
+
.recaptcha-container {
|
|
225
|
+
margin: 20px 0;
|
|
226
|
+
padding: 20px;
|
|
227
|
+
display: flex;
|
|
228
|
+
flex-direction: column;
|
|
229
|
+
align-items: center;
|
|
230
|
+
border: 1px solid #e9ecef;
|
|
231
|
+
border-radius: 8px;
|
|
232
|
+
background: #f8f9fa;
|
|
233
|
+
transition: all 0.3s ease;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.recaptcha-container .g-recaptcha {
|
|
237
|
+
margin: 10px 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* Estado verificado */
|
|
241
|
+
.recaptcha-container.recaptcha-verified {
|
|
242
|
+
border-color: #28a745;
|
|
243
|
+
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.recaptcha-container.recaptcha-verified::before {
|
|
247
|
+
content: '✅ Verificado com Sucesso';
|
|
248
|
+
display: block;
|
|
249
|
+
color: #155724;
|
|
250
|
+
font-weight: bold;
|
|
251
|
+
margin-bottom: 10px;
|
|
252
|
+
font-size: 14px;
|
|
253
|
+
text-align: center;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Estado de erro */
|
|
257
|
+
.recaptcha-container.recaptcha-error {
|
|
258
|
+
border-color: #dc3545;
|
|
259
|
+
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.recaptcha-container.recaptcha-error::before {
|
|
263
|
+
content: '❌ Erro na Verificação';
|
|
264
|
+
display: block;
|
|
265
|
+
color: #721c24;
|
|
266
|
+
font-weight: bold;
|
|
267
|
+
margin-bottom: 10px;
|
|
268
|
+
font-size: 14px;
|
|
269
|
+
text-align: center;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* Animações */
|
|
273
|
+
.recaptcha-status .alert,
|
|
274
|
+
.recaptcha-error .alert {
|
|
275
|
+
margin-bottom: 0;
|
|
276
|
+
animation: slideDown 0.3s ease-out;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@keyframes slideDown {
|
|
280
|
+
from {
|
|
281
|
+
opacity: 0;
|
|
282
|
+
transform: translateY(-10px);
|
|
283
|
+
}
|
|
284
|
+
to {
|
|
285
|
+
opacity: 1;
|
|
286
|
+
transform: translateY(0);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* Responsivo */
|
|
291
|
+
@media (max-width: 576px) {
|
|
292
|
+
.recaptcha-container {
|
|
293
|
+
padding: 15px;
|
|
294
|
+
margin: 15px 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.recaptcha-container .g-recaptcha {
|
|
298
|
+
transform: scale(0.9);
|
|
299
|
+
transform-origin: center;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* Integração com formulários */
|
|
304
|
+
.recaptcha-container + form button[type="submit"].disabled,
|
|
305
|
+
.recaptcha-container + form input[type="submit"].disabled {
|
|
306
|
+
opacity: 0.6;
|
|
307
|
+
cursor: not-allowed;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* Classes CSS personalizadas */
|
|
311
|
+
.recaptcha-container.text-center {
|
|
312
|
+
text-align: center;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.recaptcha-container.my-4 {
|
|
316
|
+
margin: 1.5rem 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.recaptcha-container.mx-auto {
|
|
320
|
+
margin-left: auto;
|
|
321
|
+
margin-right: auto;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Tema escuro */
|
|
325
|
+
.recaptcha-container[data-theme="dark"] {
|
|
326
|
+
background: #343a40;
|
|
327
|
+
border-color: #495057;
|
|
328
|
+
color: #fff;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.recaptcha-container[data-theme="dark"].recaptcha-verified {
|
|
332
|
+
background: linear-gradient(135deg, #1e5c3a 0%, #28a745 100%);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.recaptcha-container[data-theme="dark"].recaptcha-error {
|
|
336
|
+
background: linear-gradient(135deg, #5c1e1e 0%, #dc3545 100%);
|
|
337
|
+
}
|
|
338
|
+
</style>
|
|
@@ -968,6 +968,11 @@
|
|
|
968
968
|
{% endif %}
|
|
969
969
|
</div>
|
|
970
970
|
</div>
|
|
971
|
+
<!-- Em vez do StreamField, use include direto -->
|
|
972
|
+
<div class="recaptcha-votacao-container">
|
|
973
|
+
{% include 'enap_designsystem/blocks/recaptcha.html' with value=recaptcha_config %}
|
|
974
|
+
</div>
|
|
975
|
+
|
|
971
976
|
<div class="sistema-votacao-container">
|
|
972
977
|
<!-- Header -->
|
|
973
978
|
|
|
@@ -1170,6 +1175,7 @@
|
|
|
1170
1175
|
<p class="sistema-progresso-text" id="progresso-text-modal">0% concluído</p>
|
|
1171
1176
|
</div>
|
|
1172
1177
|
{% endif %}
|
|
1178
|
+
|
|
1173
1179
|
|
|
1174
1180
|
<div class="sistema-modal-actions">
|
|
1175
1181
|
<button class="sistema-btn-continuar" onclick="SistemaVotacao.continuarVotacao()">
|
enap_designsystem/views.py
CHANGED
|
@@ -119,7 +119,8 @@ def callback_sso(request):
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
# ⚠️ Desativa verificação SSL apenas em DEV
|
|
122
|
-
verify_ssl = not settings.DEBUG
|
|
122
|
+
#verify_ssl = not settings.DEBUG
|
|
123
|
+
verify_ssl = False
|
|
123
124
|
|
|
124
125
|
# 🔐 Solicita o token
|
|
125
126
|
print("📥 Enviando para /token:", data)
|