Skip to content

Commit bd2353e

Browse files
authored
Merge pull request #1
Implement Turbo for Form Ignore and Layout Updates
2 parents 666af1a + 7afa9fc commit bd2353e

File tree

15 files changed

+330
-527
lines changed

15 files changed

+330
-527
lines changed

app/controllers/cvs_controller.ts

Lines changed: 122 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ import fs from 'fs/promises'
44
import env from '#start/env'
55

66
export default class CvsController {
7-
public async show({ view }: HttpContext) {
8-
return view.render('pages/new');
7+
public async index({ view }: HttpContext) {
8+
return view.render('pages/home');
9+
}
10+
11+
public async create({ view }: HttpContext) {
12+
return view.render('pages/create');
913
}
1014

1115
public async generate({ request, response, view }: HttpContext) {
@@ -14,32 +18,32 @@ export default class CvsController {
1418

1519
const toArray = (value: any) => (Array.isArray(value) ? value : value ? [value] : [])
1620

17-
const firstName = request.input('first_name')
18-
const lastName = request.input('last_name')
19-
const birthdate = request.input('birthdate')
20-
const city = request.input('city')
21-
const phone = request.input('phone')
22-
const email = request.input('email')
23-
const jobTitle = request.input('job_title')
24-
const profile = request.input('profile')
25-
const profilePicture = request.file('photo')
26-
27-
const positions = toArray(request.input('position')) ?? []
28-
const companies = toArray(request.input('company')) ?? []
29-
const locations = toArray(request.input('location')) ?? []
30-
const startDates = toArray(request.input('start_date')) ?? []
31-
const endDates = toArray(request.input('end_date')) ?? []
32-
const descriptions = toArray(request.input('description')) ?? []
33-
34-
const degrees = toArray(request.input('degree')) ?? []
35-
const institutions = toArray(request.input('institution')) ?? []
36-
const locationEdus = toArray(request.input('location_edu')) ?? []
37-
const startDateEdus = toArray(request.input('start_date_edu')) ?? []
38-
const endDateEdus = toArray(request.input('end_date_edu')) ?? []
39-
const descriptionEdus = toArray(request.input('description_edu')) ?? []
40-
41-
const skills = toArray(request.input('skill')) ?? []
42-
const skillLevels = toArray(request.input('skill_level')) ?? []
21+
let firstName = request.input('first_name')
22+
let lastName = request.input('last_name')
23+
let birthdate = request.input('birthdate')
24+
let city = request.input('city')
25+
let phone = request.input('phone')
26+
let email = request.input('email')
27+
let jobTitle = request.input('job_title')
28+
let profile = request.input('profile')
29+
let profilePicture = request.file('photo')
30+
31+
let positions = toArray(request.input('position')) ?? []
32+
let companies = toArray(request.input('company')) ?? []
33+
let locations = toArray(request.input('location')) ?? []
34+
let startDates = toArray(request.input('start_date')) ?? []
35+
let endDates = toArray(request.input('end_date')) ?? []
36+
let descriptions = toArray(request.input('description')) ?? []
37+
38+
let degrees = toArray(request.input('degree')) ?? []
39+
let institutions = toArray(request.input('institution')) ?? []
40+
let locationEdus = toArray(request.input('location_edu')) ?? []
41+
let startDateEdus = toArray(request.input('start_date_edu')) ?? []
42+
let endDateEdus = toArray(request.input('end_date_edu')) ?? []
43+
let descriptionEdus = toArray(request.input('description_edu')) ?? []
44+
45+
let skills = toArray(request.input('skill')) ?? []
46+
let skillLevels = toArray(request.input('skill_level')) ?? []
4347

4448
if (profilePicture && profilePicture.headers && profilePicture.headers['content-type']) {
4549
contentType = profilePicture.headers['content-type']
@@ -88,10 +92,6 @@ export default class CvsController {
8892
skillSet: skillSet
8993
})
9094

91-
if (env.get('NODE_ENV') === 'development') {
92-
return html
93-
}
94-
9595
const browser: Browser = await puppeteer.launch({
9696
headless: true,
9797
args: ['--no-sandbox', '--disable-setuid-sandbox'],
@@ -109,4 +109,94 @@ export default class CvsController {
109109

110110
return response.send(pdfBuffer)
111111
}
112+
113+
public async preview({ view, params }: HttpContext) {
114+
const templateId = params.templateId
115+
116+
let template = ''
117+
switch (templateId) {
118+
case '1':
119+
template = 'cv_template_1'
120+
break
121+
case '2':
122+
template = 'cv_template_2'
123+
break
124+
default:
125+
return 'Not found!' // TODO do decent error codes
126+
}
127+
128+
console.log(template)
129+
const demoData = await this.demoData()
130+
131+
const workExperience = demoData.positions.map((_, i) => ({
132+
position: demoData.positions[i],
133+
company: demoData.companies[i],
134+
location: demoData.locations[i],
135+
start_date: demoData.startDates[i],
136+
end_date: demoData.endDates[i],
137+
description: demoData.descriptions[i],
138+
}))
139+
140+
const educations = demoData.degrees.map((_, i) => ({
141+
degree: demoData.degrees[i],
142+
institution: demoData.institutions[i],
143+
location: demoData.locationEdus[i],
144+
start_date: demoData.startDateEdus[i],
145+
end_date: demoData.endDateEdus[i],
146+
description: demoData.descriptionEdus[i],
147+
}))
148+
149+
const skillSet = demoData.skills.map((name, i) => ({
150+
name,
151+
level: demoData.skillLevels[i],
152+
}))
153+
154+
const html = await view.render(`pages/templates/${template}`, {
155+
firstName: demoData.firstName,
156+
lastName: demoData.lastName,
157+
birthdate: demoData.birthdate,
158+
city: demoData.city,
159+
phone: demoData.phone,
160+
email: demoData.email,
161+
jobTitle: demoData.jobTitle,
162+
profile: demoData.profile,
163+
profilePicture: 'https://randomuser.me/api/portraits/men/75.jpg',
164+
workExperience,
165+
educations,
166+
skillSet,
167+
})
168+
169+
return html
170+
}
171+
172+
demoData() {
173+
return {
174+
firstName: 'John',
175+
lastName: 'Doe',
176+
birthdate: '1990-01-01',
177+
city: 'Amsterdam',
178+
phone: '+31 6 12345678',
179+
email: 'john.doe@example.com',
180+
jobTitle: 'Software Engineer',
181+
profile: 'Passionate developer with 10+ years of experience in web development.',
182+
183+
positions: ['Frontend Developer'],
184+
companies: ['Tech Corp'],
185+
locations: ['Amsterdam'],
186+
startDates: ['2020-01'],
187+
endDates: ['2023-06'],
188+
descriptions: ['Worked on frontend features and improved performance.'],
189+
190+
degrees: ['BSc Computer Science'],
191+
institutions: ['University of Amsterdam'],
192+
locationEdus: ['Amsterdam'],
193+
startDateEdus: ['2010-09'],
194+
endDateEdus: ['2014-06'],
195+
descriptionEdus: ['Studied algorithms, data structures, and web technologies.'],
196+
197+
skills: ['JavaScript', 'TypeScript', 'Vue.js'],
198+
skillLevels: [3, 1, 4],
199+
}
200+
}
201+
112202
}

public/images/icons/cv.png

21.2 KB
Loading

public/images/icons/cv2.png

36 KB
Loading

resources/css/app.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
@tailwind base;
22
@tailwind components;
33
@tailwind utilities;
4+
5+
body {
6+
font-family: 'Inter', sans-serif;
7+
}
8+

resources/js/app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Alpine from 'alpinejs';
2-
//import * as Turbo from '@hotwired/turbo';
2+
import * as Turbo from '@hotwired/turbo';
33

44
window.Alpine = Alpine;
55
Alpine.start();
66

7-
//window.Turbo = Turbo;
7+
window.Turbo = Turbo;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>
6+
{{ title || "Your default title" }}
7+
</title>
8+
9+
@include('partials/meta')
10+
11+
@if ($slots.meta)
12+
{{{ await $slots.meta() }}}
13+
@endif
14+
15+
<link rel="icon" type="image/x-icon" href="/images/icons/cv2.png">
16+
17+
<link rel="preconnect" href="https://fonts.bunny.net" />
18+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
19+
20+
<script src="https://cdn.tailwindcss.com"></script> <!-- TODO: Use NPM for TailwindCSS instead of CDN -->
21+
@vite(['resources/css/app.css', 'resources/js/app.js'])
22+
@stack('dumper')
23+
</head>
24+
25+
<body class="min-h-screen w-screen font-sans bg-purple-900 text-white">
26+
@include('partials/navbar')
27+
28+
{{{ await $slots.main() }}}
29+
30+
@include('partials/footer')
31+
</body>
32+
</html>
Lines changed: 41 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,7 @@
1-
<!DOCTYPE html>
2-
<html>
3-
<head>
4-
<meta charset="utf-8" />
5-
<meta name="viewport" content="width=device-width, initial-scale=1" />
6-
<title>KlikCV</title>
7-
8-
<link rel="preconnect" href="https://fonts.bunny.net" />
9-
<link
10-
href="https://fonts.bunny.net/css?family=instrument-sans:400,400i,500,500i,600,600i,700,700i"
11-
rel="stylesheet"
12-
/>
13-
<script src="https://cdn.tailwindcss.com"></script>
14-
15-
@vite(['resources/css/app.css', 'resources/js/app.js'])
16-
@stack('dumper')
17-
</head>
18-
19-
<body class="min-h-screen w-screen font-sans bg-purple-900 text-white">
20-
<header class="bg-purple-800 border-b border-purple-700 shadow-md">
21-
<div class="max-w-7xl mx-auto px-6 py-4 flex justify-between items-center">
22-
<h1 class="text-lg font-bold text-white">KlikCV <span class="text-purple-300">door Kelvin de Reus</span></h1>
23-
<nav class="space-x-4">
24-
<a href="#" class="text-purple-200 hover:text-white transition">Home</a>
25-
</nav>
26-
</div>
27-
</header>
28-
29-
<div
30-
class="fixed xl:absolute left-8 right-8 top-0 bottom-0 xl:inset-0 max-w-screen-xl mx-auto pointer-events-none before:content-[''] before:[background:repeating-linear-gradient(0deg,var(--sand-5)_0_4px,transparent_0_8px)] before:absolute before:top-0 before:left-0 before:h-full before:w-px after:content-[''] after:[background:repeating-linear-gradient(0deg,var(--sand-5)_0_4px,transparent_0_8px)] after:absolute after:top-0 after:right-0 after:h-full after:w-px"
31-
>
32-
</div>
33-
1+
@layout.main({ title: "Home"})
2+
@slot('main')
343
<div class="pt-4 h-full flex flex-col mt-8 ">
35-
<form class="max-w-3xl mx-auto p-6 bg-purple-800 rounded-lg shadow-md space-y-8" method="POST" action="/generate" enctype="multipart/form-data">
4+
<form class="max-w-3xl mx-auto p-6 bg-purple-800 rounded-lg shadow-md space-y-8" data-turbo="false" method="POST" action="/generate" enctype="multipart/form-data">
365
{{ csrfField() }}
376
<fieldset class="border border-purple-700 rounded-md p-4">
387
<legend class="text-xl font-semibold mb-4">Templaten</legend>
@@ -119,16 +88,16 @@
11988

12089
<fieldset
12190
x-data="{
122-
experiences: [
123-
{ position: '', company: '', location: '', start_date: '', end_date: '', description: '' }
124-
],
125-
addExperience() {
126-
this.experiences.push({ position: '', company: '', location: '', start_date: '', end_date: '', description: '' });
127-
},
128-
removeExperience(index) {
129-
this.experiences.splice(index, 1);
130-
}
131-
}"
91+
experiences: [
92+
{ position: '', company: '', location: '', start_date: '', end_date: '', description: '' }
93+
],
94+
addExperience() {
95+
this.experiences.push({ position: '', company: '', location: '', start_date: '', end_date: '', description: '' });
96+
},
97+
removeExperience(index) {
98+
this.experiences.splice(index, 1);
99+
}
100+
}"
132101
class="border border-purple-700 rounded-md p-4 space-y-4"
133102
>
134103
<legend class="text-xl font-semibold mb-4">Werkervaring</legend>
@@ -190,16 +159,16 @@
190159

191160
<fieldset
192161
x-data="{
193-
educations: [
194-
{ degree: '', institution: '', location: '', start_date: '', end_date: '', description: '' }
195-
],
196-
addEducation() {
197-
this.educations.push({ degree: '', institution: '', location: '', start_date: '', end_date: '', description: '' });
198-
},
199-
removeEducation(index) {
200-
this.educations.splice(index, 1);
201-
}
202-
}"
162+
educations: [
163+
{ degree: '', institution: '', location: '', start_date: '', end_date: '', description: '' }
164+
],
165+
addEducation() {
166+
this.educations.push({ degree: '', institution: '', location: '', start_date: '', end_date: '', description: '' });
167+
},
168+
removeEducation(index) {
169+
this.educations.splice(index, 1);
170+
}
171+
}"
203172
class="border border-purple-700 rounded-md p-4 space-y-4"
204173
>
205174
<legend class="text-xl font-semibold mb-4">Opleiding</legend>
@@ -262,19 +231,19 @@
262231

263232
<fieldset
264233
x-data="{
265-
skills: [
266-
{ name: '', level: 0 }
267-
],
268-
addSkill() {
269-
this.skills.push({ name: '', level: 0 });
270-
},
271-
removeSkill(index) {
272-
if (this.skills.length > 1) this.skills.splice(index, 1);
273-
},
274-
setLevel(skill, level) {
275-
skill.level = level;
276-
}
277-
}"
234+
skills: [
235+
{ name: '', level: 0 }
236+
],
237+
addSkill() {
238+
this.skills.push({ name: '', level: 0 });
239+
},
240+
removeSkill(index) {
241+
if (this.skills.length > 1) this.skills.splice(index, 1);
242+
},
243+
setLevel(skill, level) {
244+
skill.level = level;
245+
}
246+
}"
278247
class="border border-purple-700 rounded-md p-4 space-y-4"
279248
>
280249
<legend class="text-xl font-semibold mb-4">Skills</legend>
@@ -309,9 +278,9 @@
309278
type="button"
310279
@click="setLevel(skill, lvl)"
311280
:class="{
312-
'bg-blue-600': skill.level >= lvl,
313-
'bg-purple-900': skill.level < lvl
314-
}"
281+
'bg-blue-600': skill.level >= lvl,
282+
'bg-purple-900': skill.level < lvl
283+
}"
315284
class="w-8 h-4 rounded transition-colors"
316285
:aria-label="'Level ' + lvl"
317286
></button>
@@ -336,13 +305,5 @@
336305
<button type="submit" class="w-full py-3 bg-blue-600 text-white rounded-md font-semibold hover:bg-blue-700 transition-colors">Genereer CV</button>
337306
</form>
338307
</div>
339-
340-
<footer class="bg-purple-800 border-t border-purple-700 py-4 mt-8">
341-
<div class="max-w-7xl mx-auto px-6 text-center text-sm text-purple-300">
342-
© 2025 KlikCV. Gemaakt door Kelvin de Reus.
343-
</div>
344-
</footer>
345-
</body>
346-
</html>
347-
348-
308+
@endslot
309+
@end

0 commit comments

Comments
 (0)