giseldo commited on
Commit
c2a4f7a
·
1 Parent(s): 2ffc269

primeira versão

Browse files
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Usa uma imagem Python como base
2
+ FROM python:3.9
3
+
4
+ # Define o diretório de trabalho dentro do contêiner
5
+ WORKDIR /app
6
+
7
+ # Copia os arquivos do projeto para o contêiner
8
+ COPY . /app
9
+
10
+ # Instala as dependências
11
+ RUN pip install -r requirements.txt
12
+
13
+ # Expõe a porta do Flask
14
+ EXPOSE 5000
15
+
16
+ # Comando para rodar o app
17
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template, redirect, url_for, session, flash
2
+ from supabase import create_client
3
+ import os
4
+ from dotenv import load_dotenv
5
+ from functools import wraps
6
+ import uuid
7
+ from datetime import datetime
8
+
9
+ # Carregar variáveis de ambiente
10
+ load_dotenv()
11
+
12
+ app = Flask(__name__)
13
+ app.secret_key = os.getenv("SECRET_KEY", "123")
14
+
15
+ # Configuração do Supabase
16
+ supabase_url = os.getenv("SUPABASE_URL")
17
+ supabase_key = os.getenv("SUPABASE_KEY")
18
+ supabase = create_client(supabase_url, supabase_key)
19
+
20
+ # Decorator para verificar se o usuário está autenticado
21
+ def login_required(f):
22
+ @wraps(f)
23
+ def decorated_function(*args, **kwargs):
24
+ if 'user' not in session:
25
+ return redirect(url_for('login', next=request.url))
26
+ return f(*args, **kwargs)
27
+ return decorated_function
28
+
29
+ # Decorator para verificar se o usuário é admin
30
+ def admin_required(f):
31
+ @wraps(f)
32
+ def decorated_function(*args, **kwargs):
33
+ if 'user' not in session:
34
+ return redirect(url_for('login', next=request.url))
35
+
36
+ # Verificar se o usuário é admin consultando a tabela de usuários
37
+ user_id = session['user']['id']
38
+ try:
39
+ response = supabase.table('profiles').select('is_admin').eq('id', user_id).execute()
40
+ if response.data and response.data[0].get('is_admin') == True:
41
+ return f(*args, **kwargs)
42
+ else:
43
+ flash('Acesso negado. Você precisa ser administrador para acessar esta página.', 'danger')
44
+ return redirect(url_for('index'))
45
+ except Exception as e:
46
+ flash(f'Erro ao verificar permissões: {str(e)}', 'danger')
47
+ return redirect(url_for('index'))
48
+
49
+ return decorated_function
50
+
51
+ # Função para obter conferências do Supabase
52
+ def get_conferences_from_db():
53
+ try:
54
+ response = supabase.table('conferences').select('*').execute()
55
+ return response.data
56
+ except Exception as e:
57
+ print(f"Erro ao obter conferências: {str(e)}")
58
+ return []
59
+
60
+ # Função para obter uma conferência específica pelo ID
61
+ def get_conference_by_id(conference_id):
62
+ try:
63
+ response = supabase.table('conferences').select('*').eq('id', conference_id).execute()
64
+ if response.data and len(response.data) > 0:
65
+ return response.data[0]
66
+ return None
67
+ except Exception as e:
68
+ print(f"Erro ao obter conferência: {str(e)}")
69
+ return None
70
+
71
+ # Rota principal
72
+ @app.route('/')
73
+ def index():
74
+ conferences = get_conferences_from_db()
75
+ return render_template('index.html', conferences=conferences)
76
+
77
+ # Rota para exibir detalhes de uma conferência específica
78
+ @app.route('/conference/<conference_id>')
79
+ def conference_details(conference_id):
80
+ conference = get_conference_by_id(conference_id)
81
+ if conference:
82
+ return render_template('conference_details.html', conference=conference)
83
+ else:
84
+ return render_template('404.html'), 404
85
+
86
+ # Rota para a página de signup
87
+ @app.route('/signup', methods=['GET', 'POST'])
88
+ def signup():
89
+ if request.method == 'POST':
90
+ email = request.form.get('email')
91
+ password = request.form.get('password')
92
+
93
+ try:
94
+ # Criar usuário no Supabase
95
+ response = supabase.auth.sign_up({
96
+ "email": email,
97
+ "password": password
98
+ })
99
+
100
+ user = response.user
101
+
102
+ if user:
103
+ return redirect(url_for('login'))
104
+ else:
105
+ return render_template('signup.html', error="Ocorreu um erro durante o registro")
106
+
107
+ except Exception as e:
108
+ return render_template('signup.html', error=str(e))
109
+
110
+ return render_template('signup.html')
111
+
112
+ # Rota para a página de login
113
+ @app.route('/login', methods=['GET', 'POST'])
114
+ def login():
115
+ if request.method == 'POST':
116
+ email = request.form.get('email')
117
+ password = request.form.get('password')
118
+
119
+ try:
120
+ # Autenticar usuário no Supabase
121
+ response = supabase.auth.sign_in_with_password({
122
+ "email": email,
123
+ "password": password
124
+ })
125
+
126
+ session['user'] = {
127
+ 'id': response.user.id,
128
+ 'email': response.user.email,
129
+ 'access_token': response.session.access_token
130
+ }
131
+
132
+ return redirect(url_for('dashboard'))
133
+
134
+ except Exception as e:
135
+ return render_template('login.html', error=str(e))
136
+
137
+ return render_template('login.html')
138
+
139
+ # Rota para o painel do usuário (protegida)
140
+ @app.route('/dashboard')
141
+ @login_required
142
+ def dashboard():
143
+ user = session.get('user')
144
+ # Verificar se o usuário é admin
145
+ try:
146
+ response = supabase.table('profiles').select('is_admin').eq('id', user['id']).execute()
147
+ is_admin = response.data and response.data[0].get('is_admin') == True
148
+ except:
149
+ is_admin = False
150
+
151
+ is_admin = True
152
+
153
+ return render_template('dashboard.html', user=user, is_admin=is_admin)
154
+
155
+ # Rota para logout
156
+ @app.route('/logout')
157
+ def logout():
158
+ # Realizar logout no Supabase
159
+ supabase.auth.sign_out()
160
+
161
+ # Limpar sessão
162
+ session.pop('user', None)
163
+
164
+ return redirect(url_for('index'))
165
+
166
+ # Rota para listar conferências (admin)
167
+ @app.route('/admin/conferences')
168
+ #@admin_required
169
+ def admin_conferences():
170
+ conferences = get_conferences_from_db()
171
+ return render_template('admin/conferences.html', conferences=conferences)
172
+
173
+ # Rota para adicionar nova conferência
174
+ @app.route('/admin/conferences/new', methods=['GET', 'POST'])
175
+ #@admin_required
176
+ def add_conference():
177
+ print("entrou aqui")
178
+ if request.method == 'POST':
179
+ try:
180
+ # Preparar dados da conferência
181
+ conference_data = {
182
+ 'name': request.form.get('name'),
183
+ 'full_name': request.form.get('full_name'),
184
+ 'dates': request.form.get('dates'),
185
+ 'location': request.form.get('location'),
186
+ 'categories': request.form.get('categories').split(','),
187
+ 'deadline': request.form.get('deadline'),
188
+ 'website': request.form.get('website'),
189
+ 'description': request.form.get('description'),
190
+ 'created_at': datetime.now().isoformat(),
191
+ 'updated_at': datetime.now().isoformat()
192
+ }
193
+
194
+ # Inserir no Supabase
195
+ response = supabase.table('conferences').insert(conference_data).execute()
196
+
197
+ flash('Conferência adicionada com sucesso!', 'success')
198
+ return redirect(url_for('admin_conferences'))
199
+
200
+ except Exception as e:
201
+ flash(f'Erro ao adicionar conferência: {str(e)}', 'danger')
202
+ return render_template('admin/conference_form.html', conference=None, action='add')
203
+
204
+ return render_template('admin/conference_form.html', conference=None, action='add')
205
+
206
+ # Rota para editar conferência
207
+ @app.route('/admin/conferences/edit/<conference_id>', methods=['GET', 'POST'])
208
+ #@admin_required
209
+ def edit_conference(conference_id):
210
+ # Obter a conferência pelo ID
211
+ conference = get_conference_by_id(conference_id)
212
+
213
+ if not conference:
214
+ flash('Conferência não encontrada', 'danger')
215
+ return redirect(url_for('admin_conferences'))
216
+
217
+ if request.method == 'POST':
218
+ try:
219
+ # Preparar dados atualizados
220
+ updated_data = {
221
+ 'name': request.form.get('name'),
222
+ 'full_name': request.form.get('full_name'),
223
+ 'dates': request.form.get('dates'),
224
+ 'location': request.form.get('location'),
225
+ 'categories': request.form.get('categories').split(','),
226
+ 'deadline': request.form.get('deadline'),
227
+ 'website': request.form.get('website'),
228
+ 'description': request.form.get('description'),
229
+ 'updated_at': datetime.now().isoformat()
230
+ }
231
+
232
+ # Atualizar no Supabase
233
+ supabase.table('conferences').update(updated_data).eq('id', conference_id).execute()
234
+
235
+ flash('Conferência atualizada com sucesso!', 'success')
236
+ return redirect(url_for('admin_conferences'))
237
+
238
+ except Exception as e:
239
+ flash(f'Erro ao atualizar conferência: {str(e)}', 'danger')
240
+ return render_template('admin/conference_form.html', conference=conference, action='edit')
241
+
242
+ return render_template('admin/conference_form.html', conference=conference, action='edit')
243
+
244
+ # Rota para excluir conferência
245
+ @app.route('/admin/conferences/delete/<conference_id>', methods=['POST'])
246
+ #@admin_required
247
+ def delete_conference(conference_id):
248
+ try:
249
+ # Excluir do Supabase
250
+ supabase.table('conferences').delete().eq('id', conference_id).execute()
251
+ flash('Conferência excluída com sucesso!', 'success')
252
+ except Exception as e:
253
+ flash(f'Erro ao excluir conferência: {str(e)}', 'danger')
254
+
255
+ return redirect(url_for('admin_conferences'))
256
+
257
+ # Rota para a página "sobre"
258
+ @app.route('/sobre')
259
+ def sobre():
260
+ return render_template('sobre.html')
261
+
262
+ # Rota para a página de contato
263
+ @app.route('/contato')
264
+ def contato():
265
+ return render_template('contato.html')
266
+
267
+ # Rota para a página de perfil do usuário
268
+ @app.route('/perfil')
269
+ @login_required
270
+ def perfil():
271
+ user = session.get('user')
272
+ return render_template('perfil.html', user=user)
273
+
274
+ if __name__ == '__main__':
275
+ app.run(debug=True)
lixo ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <h4 class="mt-4">Tópicos principais:</h4>
2
+ <ul class="list-group list-group-flush mb-4">
3
+ {% for topic in conference.topics %}
4
+ <li class="list-group-item">{{ topic }}</li>
5
+ {% endfor %}
6
+ </ul>
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ flask==2.3.3
2
+ supabase==2.0.3
3
+ python-dotenv==1.0.0
4
+ gunicorn==21.2.0
5
+ uuid==1.30
static/js/scripts.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function calculateTimeRemaining(deadline) {
2
+ const deadlineDate = new Date(deadline);
3
+ const now = new Date();
4
+ const timeDiff = deadlineDate - now;
5
+
6
+ if (timeDiff <= 0) {
7
+ return 'Deadline passed';
8
+ }
9
+
10
+ const hours = Math.floor(timeDiff / (1000 * 60 * 60));
11
+ const minutes = Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60));
12
+ const seconds = Math.floor((timeDiff % (1000 * 60)) / 1000);
13
+
14
+ return `in about ${hours} hours, ${minutes} minutes, and ${seconds} seconds`;
15
+ }
templates/404.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "layout.html" %}
2
+
3
+ {% block title %}Página não encontrada{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container text-center mt-5">
7
+ <div class="alert alert-danger">
8
+ <h1><i class="fas fa-exclamation-triangle"></i> Oops!</h1>
9
+ <h2>Página não encontrada</h2>
10
+ <p class="lead">A página que você está procurando não existe ou foi removida.</p>
11
+ <a href="{{ url_for('index') }}" class="btn btn-primary">Voltar para Home</a>
12
+ </div>
13
+ </div>
14
+ {% endblock %}
templates/admin/conference_form.html ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "layout.html" %}
2
+
3
+ {% block title %}
4
+ {% if action == 'add' %}
5
+ Nova Conferência
6
+ {% else %}
7
+ Editar Conferência
8
+ {% endif %}
9
+ {% endblock %}
10
+
11
+ {% block content %}
12
+ <div class="container mt-4">
13
+ <nav aria-label="breadcrumb">
14
+ <ol class="breadcrumb">
15
+ <li class="breadcrumb-item"><a href="{{ url_for('dashboard') }}">Dashboard</a></li>
16
+ <li class="breadcrumb-item"><a href="{{ url_for('admin_conferences') }}">Conferências</a></li>
17
+ <li class="breadcrumb-item active" aria-current="page">
18
+ {% if action == 'add' %}Nova Conferência{% else %}Editar Conferência{% endif %}
19
+ </li>
20
+ </ol>
21
+ </nav>
22
+
23
+ <div class="card">
24
+ <div class="card-header bg-primary text-white">
25
+ <h2>{% if action == 'add' %}Nova Conferência{% else %}Editar Conferência{% endif %}</h2>
26
+ </div>
27
+ <div class="card-body">
28
+ {% with messages = get_flashed_messages(with_categories=true) %}
29
+ {% if messages %}
30
+ {% for category, message in messages %}
31
+ <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
32
+ {{ message }}
33
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Fechar"></button>
34
+ </div>
35
+ {% endfor %}
36
+ {% endif %}
37
+ {% endwith %}
38
+
39
+ <form method="POST" id="conferenceForm">
40
+ <div class="row mb-3">
41
+ <div class="col-md-6">
42
+ <label for="name" class="form-label">Nome Curto*</label>
43
+ <input type="text" class="form-control" id="name" name="name"
44
+ value="{{ conference.name if conference else '' }}" required>
45
+ </div>
46
+ <div class="col-md-6">
47
+ <label for="full_name" class="form-label">Nome Completo*</label>
48
+ <input type="text" class="form-control" id="full_name" name="full_name"
49
+ value="{{ conference.full_name if conference else '' }}" required>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="row mb-3">
54
+ <div class="col-md-6">
55
+ <label for="dates" class="form-label">Datas*</label>
56
+ <input type="text" class="form-control" id="dates" name="dates"
57
+ value="{{ conference.dates if conference else '' }}" required>
58
+ </div>
59
+ <div class="col-md-6">
60
+ <label for="location" class="form-label">Local*</label>
61
+ <input type="text" class="form-control" id="location" name="location"
62
+ value="{{ conference.location if conference else '' }}" required>
63
+ </div>
64
+ </div>
65
+
66
+ <div class="row mb-3">
67
+ <div class="col-md-6">
68
+ <label for="deadline" class="form-label">Prazo*</label>
69
+ <input type="text" class="form-control" id="deadline" name="deadline"
70
+ value="{{ conference.deadline if conference else '' }}" required
71
+ placeholder="YYYY-MM-DD HH:MM">
72
+ </div>
73
+ <div class="col-md-6">
74
+
75
+ </div>
76
+ </div>
77
+
78
+ <div class="mb-3">
79
+ <label for="categories" class="form-label">Categorias*</label>
80
+ <input type="text" class="form-control" id="categories" name="categories"
81
+ value="{% if conference %}{{ conference.categories|join(',') }}{% endif %}" required
82
+ placeholder="machine-learning, natural-language-processing">
83
+ <div class="form-text">Separadas por vírgula, sem espaços.</div>
84
+ </div>
85
+
86
+ <div class="mb-3">
87
+ <label for="website" class="form-label">Website*</label>
88
+ <input type="text" class="form-control" id="website" name="website"
89
+ value="{{ conference.website if conference else '' }}" required
90
+ placeholder="exemplo.com">
91
+ </div>
92
+
93
+ <div class="mb-3">
94
+ <label for="description" class="form-label">Descrição*</label>
95
+ <textarea class="form-control" id="description" name="description" rows="5" required>{{ conference.description if conference else '' }}</textarea>
96
+ </div>
97
+
98
+ <div class="d-grid gap-2 d-md-flex justify-content-md-end">
99
+ <a href="{{ url_for('admin_conferences') }}" class="btn btn-secondary me-md-2">Cancelar</a>
100
+ <button type="submit" class="btn btn-primary">
101
+ {% if action == 'add' %}Adicionar Conferência{% else %}Salvar Alterações{% endif %}
102
+ </button>
103
+ </div>
104
+ </form>
105
+ </div>
106
+ </div>
107
+ </div>
108
+
109
+ {% endblock %}
templates/admin/conferences.html ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "layout.html" %}
2
+
3
+ {% block title %}Gerenciamento de Conferências - Admin{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-4">
7
+ <h2 class="mb-4">Gerenciamento de Conferências</h2>
8
+
9
+ {% with messages = get_flashed_messages(with_categories=true) %}
10
+ {% if messages %}
11
+ {% for category, message in messages %}
12
+ <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
13
+ {{ message }}
14
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Fechar"></button>
15
+ </div>
16
+ {% endfor %}
17
+ {% endif %}
18
+ {% endwith %}
19
+
20
+ <div class="d-flex justify-content-between align-items-center mb-3">
21
+ <h4>Conferências Cadastradas</h4>
22
+ <a href="{{ url_for('add_conference') }}" class="btn btn-primary">
23
+ <i class="fas fa-plus"></i> Nova Conferência
24
+ </a>
25
+ </div>
26
+
27
+ <div class="table-responsive">
28
+ <table class="table table-striped table-hover">
29
+ <thead class="table-dark">
30
+ <tr>
31
+ <th>ID</th>
32
+ <th>Nome</th>
33
+ <th>Local</th>
34
+ <th>Data</th>
35
+ <th>Prazo</th>
36
+ <th>Ações</th>
37
+ </tr>
38
+ </thead>
39
+ <tbody>
40
+ {% for conference in conferences %}
41
+ <tr>
42
+ <td>{{ conference.id }}</td>
43
+ <td>{{ conference.name }}</td>
44
+ <td>{{ conference.location }}</td>
45
+ <td>{{ conference.dates }}</td>
46
+ <td>{{ conference.deadline }}</td>
47
+ <td>
48
+ <div class="btn-group" role="group">
49
+ <a href="{{ url_for('conference_details', conference_id=conference.id) }}"
50
+ class="btn btn-sm btn-info" title="Visualizar">
51
+ <i class="fas fa-eye"></i>
52
+ </a>
53
+ <a href="{{ url_for('edit_conference', conference_id=conference.id) }}"
54
+ class="btn btn-sm btn-warning" title="Editar">
55
+ <i class="fas fa-edit"></i>
56
+ </a>
57
+ <button type="button" class="btn btn-sm btn-danger" title="Excluir"
58
+ data-bs-toggle="modal" data-bs-target="#deleteModal{{ conference.id }}">
59
+ <i class="fas fa-trash"></i>
60
+ </button>
61
+ </div>
62
+
63
+ <!-- Modal de confirmação para exclusão -->
64
+ <div class="modal fade" id="deleteModal{{ conference.id }}" tabindex="-1" aria-hidden="true">
65
+ <div class="modal-dialog">
66
+ <div class="modal-content">
67
+ <div class="modal-header">
68
+ <h5 class="modal-title">Confirmar Exclusão</h5>
69
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fechar"></button>
70
+ </div>
71
+ <div class="modal-body">
72
+ <p>Tem certeza de que deseja excluir a conferência <strong>{{ conference.name }}</strong>?</p>
73
+ <p class="text-danger">Esta ação não pode ser desfeita.</p>
74
+ </div>
75
+ <div class="modal-footer">
76
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
77
+ <form action="{{ url_for('delete_conference', conference_id=conference.id) }}" method="POST">
78
+ <button type="submit" class="btn btn-danger">Excluir</button>
79
+ </form>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </td>
85
+ </tr>
86
+ {% else %}
87
+ <tr>
88
+ <td colspan="6" class="text-center">Nenhuma conferência cadastrada.</td>
89
+ </tr>
90
+ {% endfor %}
91
+ </tbody>
92
+ </table>
93
+ </div>
94
+
95
+ <div class="mt-3">
96
+ <a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Voltar para Dashboard</a>
97
+ </div>
98
+ </div>
99
+ {% endblock %}
templates/conference_details.html ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "layout.html" %}
2
+
3
+ {% block title %}{{ conference.name }} - Detalhes{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-4">
7
+ <nav aria-label="breadcrumb">
8
+ <ol class="breadcrumb">
9
+ <li class="breadcrumb-item"><a href="{{ url_for('index') }}">Home</a></li>
10
+ <li class="breadcrumb-item active" aria-current="page">{{ conference.name }}</li>
11
+ </ol>
12
+ </nav>
13
+
14
+ <div class="card mb-4">
15
+ <div class="card-header bg-primary text-white">
16
+ <h1 class="card-title">{{ conference.name }}</h1>
17
+ <h3 class="card-subtitle mb-2">{{ conference.full_name }}</h3>
18
+ </div>
19
+ <div class="card-body">
20
+ <div class="row">
21
+ <div class="col-md-8">
22
+ <p class="card-text">{{ conference.description }}</p>
23
+
24
+
25
+ </div>
26
+ <div class="col-md-4">
27
+ <div class="card mb-3">
28
+ <div class="card-header bg-light">Informações</div>
29
+ <ul class="list-group list-group-flush">
30
+ <li class="list-group-item"><strong>Datas:</strong> {{ conference.dates }}</li>
31
+ <li class="list-group-item"><strong>Local:</strong> {{ conference.location }}</li>
32
+ <li class="list-group-item"><strong>Prazo:</strong> {{ conference.deadline }}</li>
33
+ <li class="list-group-item"><strong>Site:</strong> <a href="https://{{ conference.website }}" target="_blank">{{ conference.website }}</a></li>
34
+ </ul>
35
+ </div>
36
+
37
+ <div class="d-grid gap-2">
38
+ <a href="https://{{ conference.website }}" target="_blank" class="btn btn-success">Visitar Site Oficial</a>
39
+ <a href="{{ url_for('index') }}" class="btn btn-outline-secondary">Voltar</a>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ {% endblock %}
templates/dashboard.html ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "layout.html" %}
2
+
3
+ {% block title %}Dashboard - Flask Supabase Auth{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="row">
7
+ <div class="col-md-12">
8
+ <div class="card">
9
+ <div class="card-header">
10
+ <h2>Dashboard</h2>
11
+ </div>
12
+ <div class="card-body">
13
+ <h5 class="card-title">Bem-vindo, {{ user.email }}!</h5>
14
+ <p class="card-text">Você está autenticado com sucesso!</p>
15
+
16
+ <div class="mt-4">
17
+ <h6>Suas informações:</h6>
18
+ <ul class="list-group">
19
+ <li class="list-group-item">
20
+ <strong>ID:</strong> {{ user.id }}
21
+ </li>
22
+ <li class="list-group-item">
23
+ <strong>Email:</strong> {{ user.email }}
24
+ </li>
25
+ </ul>
26
+ </div>
27
+
28
+ {% if is_admin %}
29
+ <div class="mt-4">
30
+ <h6>Recursos de Administrador:</h6>
31
+ <div class="list-group">
32
+ <a href="{{ url_for('admin_conferences') }}" class="list-group-item list-group-item-action">
33
+ <i class="fas fa-calendar-alt"></i> Gerenciar Conferências
34
+ </a>
35
+ <a href="{{ url_for('add_conference') }}" class="list-group-item list-group-item-action">
36
+ <i class="fas fa-plus-circle"></i> Cadastrar Nova Conferência
37
+ </a>
38
+ </div>
39
+ </div>
40
+ {% endif %}
41
+
42
+ <div class="mt-4">
43
+ <a href="{{ url_for('logout') }}" class="btn btn-danger">Sair</a>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ {% endblock %}
templates/index.html ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "layout.html" %}
2
+
3
+ {% block title %}Home - Flask Supabase Auth{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="p-5 mb-4 bg-light rounded-3">
7
+ <div class="container-fluid py-5 text-center">
8
+ <h1 class="display-5 fw-bold">AI Conferences deadline</h1>
9
+ <p class="fs-4">Seu calendário completo de eventos em Ciência da Computação.</p>
10
+ {% if not session.get('user') %}
11
+ <div class="mt-4">
12
+ <a href="{{ url_for('signup') }}" class="btn btn-primary btn-lg mx-2">Registrar</a>
13
+ <a href="{{ url_for('login') }}" class="btn btn-outline-primary btn-lg mx-2">Entrar</a>
14
+ </div>
15
+ {% else %}
16
+ <div class="mt-4">
17
+ <a href="{{ url_for('dashboard') }}" class="btn btn-success btn-lg">Acessar Dashboard</a>
18
+ </div>
19
+ {% endif %}
20
+ </div>
21
+ </div>
22
+
23
+ <!-- Seção de Eventos/Conferências -->
24
+ <div class="container mt-5">
25
+ <h2 class="mb-4 text-center">Conferências de IA Disponíveis</h2>
26
+ <div class="table-responsive">
27
+ <table class="table table-hover">
28
+ <thead class="table-dark">
29
+ <tr>
30
+ <th>Nome</th>
31
+ <th>Datas</th>
32
+ <th>Localização</th>
33
+ <th>Prazo</th>
34
+ <th>Tempo Restante</th>
35
+ <th>Ações</th>
36
+ </tr>
37
+ </thead>
38
+ <tbody>
39
+ {% for conference in conferences %}
40
+ <tr>
41
+ <td>{{ conference.name }} - {{ conference.full_name }}</td>
42
+ <td>{{ conference.dates }}</td>
43
+ <td>{{ conference.location }}</td>
44
+ <td>{{ conference.deadline }}</td>
45
+ <td class="countdown-cell" data-deadline="{{ conference.deadline }}">
46
+ <span class="countdown-value">Calculando...</span>
47
+ </td>
48
+ <td>
49
+ <a href="{{ url_for('conference_details', conference_id=conference.id) }}"
50
+ class="btn btn-primary btn-sm">Ver Detalhes</a>
51
+ </td>
52
+ </tr>
53
+ {% endfor %}
54
+ </tbody>
55
+ </table>
56
+ </div>
57
+ </div>
58
+
59
+ <script>
60
+ document.addEventListener('DOMContentLoaded', function() {
61
+ // Atualizar os contadores de tempo restante
62
+ function updateCountdowns() {
63
+ const countdownCells = document.querySelectorAll('.countdown-cell');
64
+
65
+ countdownCells.forEach(cell => {
66
+ const deadlineStr = cell.getAttribute('data-deadline');
67
+ const countdownElement = cell.querySelector('.countdown-value');
68
+
69
+ // Verificar se a data é válida
70
+ if (!deadlineStr) {
71
+ countdownElement.textContent = 'Data não definida';
72
+ return;
73
+ }
74
+
75
+ try {
76
+ // Formato esperado: YYYY-MM-DD HH:MM
77
+ const deadlineDate = new Date(deadlineStr);
78
+ const now = new Date();
79
+
80
+ // Verificar se a data é válida
81
+ if (isNaN(deadlineDate.getTime())) {
82
+ countdownElement.textContent = 'Data inválida';
83
+ return;
84
+ }
85
+
86
+ const timeDiff = deadlineDate - now;
87
+
88
+ if (timeDiff <= 0) {
89
+ countdownElement.textContent = 'Prazo encerrado';
90
+ cell.classList.add('text-danger');
91
+ } else {
92
+ // Calcular dias, horas, minutos restantes
93
+ const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
94
+ const hours = Math.floor((timeDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
95
+ const minutes = Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60));
96
+
97
+ let countdownText = '';
98
+ if (days > 0) countdownText += `${days} dia(s) `;
99
+ if (hours > 0 || days > 0) countdownText += `${hours} hora(s) `;
100
+ countdownText += `${minutes} minuto(s)`;
101
+
102
+ countdownElement.textContent = countdownText;
103
+
104
+ // Adicionar classes para destacar prazos próximos
105
+ if (days < 1) {
106
+ cell.classList.add('text-danger', 'fw-bold');
107
+ } else if (days < 7) {
108
+ cell.classList.add('text-warning');
109
+ }
110
+ }
111
+ } catch (error) {
112
+ countdownElement.textContent = 'Erro ao calcular';
113
+ console.error("Erro ao calcular tempo restante:", error);
114
+ }
115
+ });
116
+ }
117
+
118
+ // Executar imediatamente e depois a cada minuto
119
+ updateCountdowns();
120
+ setInterval(updateCountdowns, 60000);
121
+ });
122
+ </script>
123
+ {% endblock %}
templates/layout.html ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="pt-br">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{% block title %}AI Conference Deadlines{% endblock %}</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
9
+ <style>
10
+ body {
11
+ padding-top: 5rem;
12
+ }
13
+ .form-container {
14
+ max-width: 500px;
15
+ margin: 0 auto;
16
+ padding: 1rem;
17
+ }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
22
+ <div class="container">
23
+ <a class="navbar-brand" href="/">AI Conference Deadlines</a>
24
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
25
+ <span class="navbar-toggler-icon"></span>
26
+ </button>
27
+ <div class="collapse navbar-collapse" id="navbarNav">
28
+ <ul class="navbar-nav ms-auto">
29
+ {% if session.get('user') %}
30
+ <li class="nav-item">
31
+ <a class="nav-link" href="{{ url_for('dashboard') }}">Dashboard</a>
32
+ </li>
33
+ <li class="nav-item">
34
+ <a class="nav-link" href="{{ url_for('logout') }}">Logout</a>
35
+ </li>
36
+ {% else %}
37
+ <li class="nav-item">
38
+ <a class="nav-link" href="{{ url_for('login') }}">Login</a>
39
+ </li>
40
+ <li class="nav-item">
41
+ <a class="nav-link" href="{{ url_for('signup') }}">Signup</a>
42
+ </li>
43
+ {% endif %}
44
+ </ul>
45
+ </div>
46
+ </div>
47
+ </nav>
48
+
49
+ <main class="container">
50
+ {% block content %}{% endblock %}
51
+ </main>
52
+
53
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
54
+ </body>
55
+ </html>
templates/login.html ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "layout.html" %}
2
+
3
+ {% block title %}Login - Flask Supabase Auth{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="form-container">
7
+ <h2 class="mb-4">Entrar</h2>
8
+
9
+ {% if error %}
10
+ <div class="alert alert-danger" role="alert">
11
+ {{ error }}
12
+ </div>
13
+ {% endif %}
14
+
15
+ <form method="POST" action="{{ url_for('login') }}">
16
+ <div class="mb-3">
17
+ <label for="email" class="form-label">Email</label>
18
+ <input type="email" class="form-control" id="email" name="email" required>
19
+ </div>
20
+ <div class="mb-3">
21
+ <label for="password" class="form-label">Senha</label>
22
+ <input type="password" class="form-control" id="password" name="password" required>
23
+ </div>
24
+ <button type="submit" class="btn btn-primary">Entrar</button>
25
+ </form>
26
+
27
+ <p class="mt-3">
28
+ Não possui uma conta? <a href="{{ url_for('signup') }}">Registre-se</a>
29
+ </p>
30
+ </div>
31
+ {% endblock %}
templates/signup.html ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "layout.html" %}
2
+
3
+ {% block title %}Registrar - Flask Supabase Auth{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="form-container">
7
+ <h2 class="mb-4">Criar Conta</h2>
8
+
9
+ {% if error %}
10
+ <div class="alert alert-danger" role="alert">
11
+ {{ error }}
12
+ </div>
13
+ {% endif %}
14
+
15
+ <form method="POST" action="{{ url_for('signup') }}">
16
+ <div class="mb-3">
17
+ <label for="email" class="form-label">Email</label>
18
+ <input type="email" class="form-control" id="email" name="email" required>
19
+ </div>
20
+ <div class="mb-3">
21
+ <label for="password" class="form-label">Senha</label>
22
+ <input type="password" class="form-control" id="password" name="password" required>
23
+ <div class="form-text">A senha deve ter pelo menos 6 caracteres.</div>
24
+ </div>
25
+ <button type="submit" class="btn btn-primary">Registrar</button>
26
+ </form>
27
+
28
+ <p class="mt-3">
29
+ Já possui uma conta? <a href="{{ url_for('login') }}">Faça login</a>
30
+ </p>
31
+ </div>
32
+ {% endblock %}