Changelog
All notable changes to the Avada Group Careers Website will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
- CV file upload on the application form (
). The applicant may now attach a PDF/DOC/DOCX instead of pasting a link; the file is base64-encoded into the submit JSON as1
_includes/form_ung_tuyen.html
. Multipart is not an option — the CRM endpoint only reads1
attachment: {filename, contentType, data}and answers 4001
c.req.json()
to anything else.1
invalid_json
- ⚠️ Deploy order is strict: the CRM must ship first. Production currently has
no
config and still has1
attachment
as1
cv_url
. Publishing this form before the CRM deploys means a file-only application is rejected with 422 and the applicant loses it. Verify with the admin API that the live form reports an1
required: true
block and1
attachment
before pushing this repo.1
cv_url required: false
- The link field is kept beside the upload and is no longer
; at least one of the two must be present, enforced client-side. Upload failing for any reason degrades to pasting a link rather than losing the application.1
required
is derived from the filename extension, never from1
contentType
: Safari and Firefox report an empty type for1
file.type
/1
.doc
, and because1
.docx
is a zip container some browsers report1
.docx
. The CRM matches the three accepted types exactly, so forwarding1
application/zip
would reject valid CVs.1
file.type
- Size and format are checked before the file is read, so a 20MB pick fails instantly instead of after a long silent wait.
- Base64 conversion uses
rather than1
FileReader.readAsDataURL()
, which blows the call stack on multi-megabyte files.1
btoa(String.fromCharCode(...new Uint8Array(buf)))
(422) is surfaced in Vietnamese with the server’s1
attachment_rejected
, and keeps everything the applicant typed.1
maxBytes
- When the CRM answers
— its attachment budget is exhausted, so it kept the application but discarded the file — the applicant is told plainly, and told where to send the CV instead. This stays a success message in green: the application did reach HR, and colouring it as an error would make someone who just submitted try again, only to be blocked by deduplication.1
{ok: true, attachmentDropped: true}
- ⚠️ Deploy order is strict: the CRM must ship first. Production currently has
no
Fixed
prefixed1
normalise()
unconditionally, so an empty link field became the literal string1
https://
once the field stopped being required — failing the CRM’s1
https://
check and rejecting applications that correctly attached a file.1
^https?://\S+$
Changed
- Applications no longer post to Slack.
had been answering1
#avada-tuyen-dung
for every submission since 24 Aug 2026 — renamed, archived, or the bot was removed — so five real applications reached HR by email but never appeared in the channel. Rather than repair the channel,1
channel_not_found
was dropped from the form config, because HR already works from the email and its1
notify.slackChannel
is the applicant. A silent channel is therefore deliberate, not a regression: re-enabling Slack means fixing the channel first.1
Reply-To
- Nothing on this site changed; the form never knew about Slack. Recorded here because the absence is otherwise indistinguishable from the outage it replaced.
- The underlying reporting gap is still open in the CRM:
stays1
status
when one channel fails and another succeeds, so neither this outage nor the August SES outage (1
forwarded
, email failed) surfaced anywhere.1
status: forwarded
- Application form now submits to the Avada Forms CRM instead of EmailJS
(
)1
_includes/form_ung_tuyen.html
. The CRM stores every submission in Firestore and then fans out to Slack (1
POST https://crm-form.avada.io/v1/forms/avada-career-application/submit
) and1
#avada-tuyen-dung
, so an application is no longer lost when the third-party mailer refuses it, and the send is no longer capped by an EmailJS quota. Form config lives in1
[email protected]
,1
avada-form-api
.1
seeds/forms-avadagroup.json
- Input names stay Vietnamese (
,1
ho_ten
, …) and are mapped to the CRM’s registered field names (1
dien_thoai
,1
name
, …) at submit time — the CRM drops any key that does not match a registered1
phone
, and prints those names verbatim into Slack and the notification email.1
FormField.name
- Failures are reported per CRM error code (
,1
rate_limited
,1
form_disabled
, …) in Vietnamese;1
validation_failed
details are mapped back to the input the applicant can actually edit, and that field is focused.1
validation_failed
- No Cloudflare Turnstile. The form is registered with
, because Avada’s single shared Turnstile site key does not allowlist the1
requireTurnstile: false
hostname — the widget can never render there, so a client-side token gate would block every real application while the server never asked for a token. Note that1
avadagroup.com
is in the Cloudflare allowlist, so a local test would have hidden this. Re-enabling means allowlisting1
localhost:4000
+1
avadagroup.com
at Cloudflare first, then1
www
on the form, and only then mounting the widget.1 2
PATCH {"requireTurnstile": true}
Added
- Client-side bot gates mirroring the avada.io forms: an off-screen honeypot
(
) and a form-open timestamp (1
_hp_field
). Neither is forwarded to the CRM. A filled honeypot means a bot, so it gets the success message without a send. A too-fast submit only asks the person to press the button again — it must never reset the form, or a real applicant who reopens the modal and submits within two seconds loses everything they typed to a fake success message.1
_form_ts
is sent with each application, so HR can see which job page (and which UTM campaign) produced the lead.1
submission_url
on the introduction textarea, matching the CRM’s cap — the server truncates a longer message silently, so the limit has to be visible while the applicant is still writing.1
maxlength="2000"
Removed
- EmailJS entirely — the
script,1
@emailjs/browser
, public key1
emailjs.init
, service1
Nk4U0-r6ahBdk0lCj
and template1
service_9ayy3zq
.1
template_re1z26m
- The hidden
input that existed only to build the EmailJS subject line.1
hide
and1
vi_tri
already carry the same two values separately, and the CRM composes the subject from them.1
thanh_vien
Fixed
was stamped from1
ngay_ung_tuyen
, which is always UTC. Vietnam is UTC+7, so every application sent before 07:00 local time was recorded with the previous day’s date. It now uses the applicant’s local calendar date.1
toISOString()
- Fixed the application form silently refusing to submit (
)1
_includes/form_ung_tuyen.html
- Root cause: native HTML constraints rejected ordinary Vietnamese input while the only feedback was an English browser tooltip that is easily missed inside the scrollable modal — pressing “Gửi ứng tuyển” appeared to do nothing at all.
required zero-padded1
ngay_sinh
, so1
dd/mm/yyyy
was rejected. The pattern now accepts1
1/1/1995
and the value is zero-padded before sending.1
d/m/yyyy
pattern was1
dien_thoai
, so1
^[0-9]+$
and1
090 123 4567
were rejected. Separators and a leading1
+84...
are now accepted and stripped before sending.1
+
was1
link_cv
, so1
type="url"
without a scheme was rejected.1
drive.google.com/...
is now prepended automatically.1
https://
- Validation messages are now in Vietnamese, shown in an in-form status area, and the first invalid field is scrolled into view.
- Submit failures now surface the real EmailJS status and message instead of a generic “Lỗi khi gửi email”, so quota/config failures are diagnosable.
- Birth date is now checked for being a real, plausible date (the relaxed pattern alone
would accept
).1
31/02/1995
Changed
- Application form “Ứng tuyển ngay” trigger changed from a bare
(no1
<a>
, so it was not keyboard focusable) to a1
href
; the modal now closes on1
<button type="button">
and restores focus to the trigger.1
Escape
- Modal wiring uses
, so every “Ứng tuyển ngay” button on a page works rather than only the first one.1
querySelectorAll
- Email field changed from
to1
type="text"
— previously1
type="email"
passed as a valid address.1
abc
- Submit button is disabled and relabelled “Đang gửi…” while a submission is in flight.
- Added
attributes and1
autocomplete
roles to the modal and its fields.1
aria
Removed
— dead code. The HTML1
validateForm()
attributes block submission before it could ever run, so its alert branch was unreachable.1
required
Added
- CLAUDE.md documentation for repository context and development guidelines
- CHANGELOG.md to track project changes
- ROADMAP.md for future feature planning
Note on the Google Forms migration
An earlier entry in this file claimed the custom form had been replaced by an embedded Google Forms iframe. That change was reverted on the same day and never shipped. The live application form is, and has remained, the custom form backed by EmailJS. History:
| Commit | Change | ||
|---|---|---|---|
|
Replace custom application form with Google Forms iframe | ||
|
Fix double scrollbar issue in Google Forms modal | ||
|
Revert to custom EmailJS application form | ||
|
Revert EmailJS configuration to original service and template | ||
|
Update EmailJS public key to match new account |
is the one job that does link out to a standalone Google Form;
it is a separate flow and is not the site-wide application form.1
_jobs/CTV-Content-SEO.md
[2024-Present] - Ongoing Maintenance
Features
- Job posting system with Jekyll collections
- Application form with EmailJS integration
- Department-based job filtering (Technical, Marketing, Sales, Service, Back Office)
- Keyword search functionality via JSON API
- Job badge system (HOT and TUYỂN GẤP badges)
- Blog/news section for company updates and internal events
- Responsive design with Bootstrap framework
Integrations
- EmailJS for email delivery (service
, template1
service_9ayy3zq
)1
template_re1z26m
- Google Analytics (UA-76130628-7)
- Disqus comments (shortname: careermageplaza)
Google Apps Script / Google Sheets logging was removed in
because of a CORS error and is no longer part of the site. Applications arrive by email only.
1 7823d79
Template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## [Version] - YYYY-MM-DD
### Added
- New features
### Changed
- Changes in existing functionality
### Deprecated
- Soon-to-be removed features
### Removed
- Removed features
### Fixed
- Bug fixes
### Security
- Security improvements

