Skip to content

Commit 45d62ad

Browse files
committed
feat: enhance CI/CD workflow to inject backend API URL and improve runtime config handling
1 parent ea97403 commit 45d62ad

3 files changed

Lines changed: 213 additions & 13 deletions

File tree

.github/workflows/frontend-ci.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,23 @@ jobs:
7676
cp -r frontend/img dist/
7777
7878
- name: Inject backend API URL
79-
if: vars.BACKEND_URL != ''
8079
run: |
81-
sed -i 's|<meta name="api-base" content="">|<meta name="api-base" content="${{ vars.BACKEND_URL }}">|g' dist/index.html
80+
BACKEND_URL="${{ vars.BACKEND_URL != '' && vars.BACKEND_URL || secrets.BACKEND_URL }}"
81+
echo "Using BACKEND_URL=${BACKEND_URL}"
82+
if [ -n "${BACKEND_URL}" ]; then
83+
sed -i 's|<meta name="api-base" content="">|<meta name="api-base" content="'"${BACKEND_URL}"'">|g' dist/index.html
84+
# write runtime config for the frontend to read at boot
85+
printf '{"apiBase":"%s"}\n' "${BACKEND_URL}" > dist/config.json
86+
else
87+
echo "No BACKEND_URL provided; leaving api-base empty"
88+
printf '{"apiBase":""}\n' > dist/config.json
89+
fi
8290
83-
- name: Require backend API URL
91+
- name: Require backend API URL (only for main deploys)
92+
if: github.ref == 'refs/heads/main'
8493
run: |
8594
if grep -q '<meta name="api-base" content="">' dist/index.html; then
86-
echo "BACKEND_URL is required for production deploys"
95+
echo "BACKEND_URL is required for production deploys on main branch"
8796
exit 1
8897
fi
8998

frontend/dist/index.html

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<!DOCTYPE html>
2+
<html lang="pt-BR">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<meta name="description" content="Backend Software Engineer Portfolio">
8+
<meta name="api-base" content="https://api.example.com">
9+
<title>Dimitri Schulz Amado</title>
10+
<link rel="stylesheet" href="css/style.css">
11+
<link rel="preconnect" href="https://fonts.googleapis.com">
12+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13+
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet">
14+
</head>
15+
16+
<body>
17+
<main class="container">
18+
19+
<!-- Hero Section -->
20+
<section id="hero" class="hero-section">
21+
<div class="profile-pic-container">
22+
<img src="img/profile-picture.jpeg" alt="Profile Picture" class="profile-pic">
23+
</div>
24+
25+
<p class="greeting">Oi! Eu sou o</p>
26+
<h1 class="name">Dimitri Schulz Amado :)<span class="cursor">|</span></h1>
27+
28+
<p class="subtitle">
29+
<span class="highlight">Sistemas</span> são o propósito e <span class="highlight">código</span> é o
30+
combustível. 🚀
31+
</p>
32+
33+
<div class="links-grid">
34+
<a href="#projects" class="link-btn">
35+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
36+
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
37+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
38+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
39+
</svg>
40+
Projetos
41+
</a>
42+
<a href="https://github.com/schulzdimitri" target="_blank" class="link-btn">
43+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
44+
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
45+
<path
46+
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22">
47+
</path>
48+
</svg>
49+
GitHub
50+
</a>
51+
<a href="https://linkedin.com/in/dimitri-schulz-amado" target="_blank" class="link-btn">
52+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
53+
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
54+
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path>
55+
<rect x="2" y="9" width="4" height="12"></rect>
56+
<circle cx="4" cy="4" r="2"></circle>
57+
</svg>
58+
LinkedIn
59+
</a>
60+
<a href="#contact" class="link-btn">
61+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
62+
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
63+
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
64+
<polyline points="22,6 12,13 2,6"></polyline>
65+
</svg>
66+
Contato
67+
</a>
68+
</div>
69+
70+
<div class="scroll-down">
71+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
72+
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
73+
<line x1="12" y1="5" x2="12" y2="19"></line>
74+
<polyline points="19 12 12 19 5 12"></polyline>
75+
</svg>
76+
</div>
77+
</section>
78+
79+
<!-- About Section -->
80+
<section id="about" class="about-section">
81+
<h2 class="section-title">Sobre mim:</h2>
82+
83+
<div class="about-grid">
84+
<div class="about-text">
85+
<p>
86+
Tenho 2 anos de experiência como desenvolvedor Fullstack, entregando soluções web ponta-a-ponta
87+
com <span class="highlight">JavaScript, jQuery, HTML, CSS</span> e o ecossistema <span
88+
class="highlight">Python (Flask e Django)</span>.
89+
</p>
90+
<p>
91+
Desde então, direcionei minha carreira definitivamente para a <span class="highlight">Engenharia
92+
de Software Backend</span>. Aprofundei meus conhecimentos em arquitetura de software
93+
explorando tecnologias corporativas robustas como <span class="highlight">Java e Spring
94+
Boot</span>, além de ferramentas avançadas do Python.
95+
</p>
96+
<p>
97+
Atualmente, minha grande paixão está na infraestrutura e na engenharia de performance. Tenho me
98+
maravilhado e dedicado fortemente ao estudo de ecossistemas conteinerizados com <span
99+
class="highlight">Docker, automação via CI/CD, programação em Golang</span> e o design de
100+
sistemas distribuídos.
101+
</p>
102+
</div>
103+
<div class="about-image-container">
104+
<img src="img/about-picture.jpeg" alt="About Picture" class="about-image">
105+
</div>
106+
</div>
107+
</section>
108+
109+
<!-- Experience Section -->
110+
<section id="experience" class="experience-section">
111+
<h2 class="section-title">Minha experiência:</h2>
112+
113+
<div class="tabs-container" id="experiences-container">
114+
<!-- Tab Headers -->
115+
<div class="tabs-list" id="experiences-tabs">
116+
<!-- Rendered dynamically from API -->
117+
</div>
118+
119+
<!-- Tab Contents -->
120+
<div class="tabs-content" id="experiences-content">
121+
<!-- Rendered dynamically from API -->
122+
</div>
123+
</div>
124+
</section>
125+
126+
<!-- Projects Section -->
127+
<section id="projects" class="projects-section">
128+
<h2 class="section-title">Meus projetos:</h2>
129+
<div class="projects-grid" id="projects-grid">
130+
<!-- Rendered dynamically from data/portfolio.json -->
131+
</div>
132+
</section>
133+
134+
<!-- Contact Section -->
135+
<section id="contact" class="contact-section">
136+
<h2 class="section-title">Contato:</h2>
137+
<form id="contact-form" class="contact-form" novalidate>
138+
<div class="form-group">
139+
<label for="contact-name">Nome</label>
140+
<input type="text" id="contact-name" name="name" placeholder="Seu nome" required>
141+
</div>
142+
<div class="form-group">
143+
<label for="contact-email">Email</label>
144+
<input type="email" id="contact-email" name="email" placeholder="seu@email.com" required>
145+
</div>
146+
<div class="form-group">
147+
<label for="contact-message">Mensagem</label>
148+
<textarea id="contact-message" name="message" rows="5" placeholder="Sua mensagem..."
149+
required></textarea>
150+
</div>
151+
<button type="submit" class="submit-btn" id="contact-submit">
152+
<span id="submit-label">Enviar mensagem</span>
153+
</button>
154+
<p class="form-feedback" id="form-feedback" aria-live="polite"></p>
155+
</form>
156+
</section>
157+
158+
</main>
159+
160+
<footer class="footer">
161+
<p>Criado por Dimitri Schulz usando HTML, CSS e JS.</p>
162+
<p>© Todos os direitos reservados.</p>
163+
</footer>
164+
165+
<script type="module" src="js/main.js"></script>
166+
</body>
167+
168+
</html>

frontend/js/main.js

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { loadProjects } from './modules/projects.js';
33
import { loadExperiences } from './modules/experiences.js';
44
import { initContactForm } from './modules/contact.js';
55

6-
function resolveApiBase() {
6+
function resolveApiBaseFromMeta() {
77
const rawMeta = document.querySelector('meta[name="api-base"]')?.content?.trim() || '';
88
const isLocalPage = ['localhost', '127.0.0.1'].includes(window.location.hostname);
9-
const metaPointsToLocalhost = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(rawMeta);
9+
const metaPointsToLocalhost = /^https?:\/\/(localhost|127\.0\.0\.1)(:\\d+)?$/i.test(rawMeta);
1010

1111
if (rawMeta && !(metaPointsToLocalhost && !isLocalPage)) {
1212
return rawMeta;
@@ -19,11 +19,34 @@ function resolveApiBase() {
1919
return '';
2020
}
2121

22-
const API_BASE = resolveApiBase();
22+
async function loadRuntimeConfig() {
23+
try {
24+
const res = await fetch('/config.json', { cache: 'no-store' });
25+
if (!res.ok) return { apiBase: '' };
26+
const json = await res.json();
27+
return { apiBase: (json && json.apiBase) ? String(json.apiBase).trim() : '' };
28+
} catch (e) {
29+
return { apiBase: '' };
30+
}
31+
}
32+
33+
async function initApp() {
34+
const cfg = await loadRuntimeConfig();
35+
const API_BASE = cfg.apiBase || resolveApiBaseFromMeta();
36+
37+
if (document.readyState === 'loading') {
38+
document.addEventListener('DOMContentLoaded', () => {
39+
initTabs();
40+
loadProjects('projects-grid', API_BASE);
41+
loadExperiences(API_BASE);
42+
initContactForm(API_BASE);
43+
});
44+
} else {
45+
initTabs();
46+
loadProjects('projects-grid', API_BASE);
47+
loadExperiences(API_BASE);
48+
initContactForm(API_BASE);
49+
}
50+
}
2351

24-
document.addEventListener('DOMContentLoaded', () => {
25-
initTabs();
26-
loadProjects('projects-grid', API_BASE);
27-
loadExperiences(API_BASE);
28-
initContactForm(API_BASE);
29-
});
52+
initApp();

0 commit comments

Comments
 (0)