Skip to content

Commit a64aef1

Browse files
authored
Merge pull request #15 from Craftserve/rozwader/pra-59-podlaczenie-formularza-kontaktowego
endpoint handling and validation review
2 parents 591180b + abf7d9a commit a64aef1

File tree

7 files changed

+150
-8
lines changed

7 files changed

+150
-8
lines changed

src/components/Button.astro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ interface Props {
33
id: string;
44
fontSize?: string;
55
variant: "primary" | "secondary" | "outlined";
6+
type: "button" | "submit" | "reset" | null | undefined,
67
}
78
8-
const { id, variant, fontSize = "1em" } = Astro.props as Props;
9+
const { id, variant, fontSize = "1em", type="button" } = Astro.props as Props;
910
1011
const variantClassMap: Record<string, string> = {
1112
primary: "dark",
@@ -15,7 +16,7 @@ const variantClassMap: Record<string, string> = {
1516
const buttonClass = variantClassMap[variant] ?? "outlined";
1617
---
1718

18-
<button type="button" id={id} class={`button ${buttonClass}`}>
19+
<button type={type} id={id} class={`button ${buttonClass}`}>
1920
<slot />
2021
</button>
2122

src/components/Textarea.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const props = Astro.props as textareaProps;
1919
resize: none;
2020
overflow-wrap: break-word;
2121
white-space: pre-wrap;
22+
width: 100%;
2223
}
2324

2425
.textarea-container {

src/scripts/api.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { FormFields } from "../types";
2+
3+
const API_URL = "https://craftserve.zendesk.com/api/v2/requests.json"
4+
5+
export const sendTicket = async (data: FormFields) => {
6+
try{
7+
const request = await fetch(API_URL, {
8+
method: "POST",
9+
headers: {
10+
"Content-Type": "application/json"
11+
},
12+
body: JSON.stringify({
13+
"request": {
14+
"requester": {
15+
"name": `${data.name} ${data.surname}`,
16+
"email": `${data.email}`
17+
},
18+
"subject": `[AGENCY] ${data.subject}`,
19+
"comment": {
20+
"body": `
21+
Firma: ${data.company}\n
22+
Numer telefonu kontaktowego: ${data.phone}\n
23+
Treść: ${data.content}
24+
`
25+
},
26+
"tags": ["agency", "contact_form"]
27+
}
28+
})
29+
})
30+
31+
const response = await request.json();
32+
33+
return response;
34+
}catch(err){
35+
console.warn(err);
36+
return "Error has occured while creating ticket";
37+
}
38+
}

src/scripts/helpers.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { FormFields } from "../types";
2+
3+
const validateEmail = (email: string) => {
4+
const emailRegExp = /^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z\d-]+(?:\.[a-z\d-]+)*\.[a-z]{2,}$/i; // np. example@gmail.com | there MUST be atleast two letters after @gmail.
5+
return email.length > 0 && emailRegExp.test(email);
6+
}
7+
8+
const validatePhone = (phone: string) => {
9+
const phoneRegExp = /^(\+?\d{2}|00\d{2})?\s?([0-9]{3}\s[0-9]{3}\s[0-9]{3}|[0-9]{9})$/;
10+
return phone.length > 0 && phoneRegExp.test(phone);
11+
}
12+
13+
export const validateContactForm = (data: FormFields) => {
14+
const array = Object.entries(data);
15+
let valid = true;
16+
17+
array.forEach((entry) => {
18+
const crop: string = entry[1].trim();
19+
valid = crop.length > 0 ? valid : false;
20+
})
21+
22+
if(!valid){
23+
return false;
24+
}
25+
26+
const isEmailValid = validateEmail(data.email.trim());
27+
const isPhoneValid = validatePhone(data.phone.trim());
28+
29+
if(!isEmailValid || !isPhoneValid){
30+
return false;
31+
}
32+
33+
return true;
34+
}

src/sections/ContactForm.astro

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import starsIconGroupWhite from "../../src/assets/starsIconGroupWhite.svg";
66
import Input from "../components/Input.astro";
77
import Button from "../components/Button.astro";
88
import { getLangFromUrl, useTranslations } from "../i18n/utils";
9+
import Textarea from "../components/Textarea.astro";
910
1011
const lang = getLangFromUrl(Astro.url);
1112
const t = useTranslations(lang);
@@ -22,7 +23,7 @@ const t = useTranslations(lang);
2223
<div class="formTopBar">
2324
<Image src={LogoDark} alt="logo" width={30} height={30} />
2425
</div>
25-
<form class="formContainer">
26+
<form class="formContainer" id="form">
2627
<div class="formSections">
2728
<div class="formTopSection">
2829
<div class="formTopSectionInput">
@@ -32,20 +33,31 @@ const t = useTranslations(lang);
3233
<Input type="text" placeholder={t("surname")} name="surname" />
3334
</div>
3435
<div class="formTopSectionInput">
35-
<Input type="tel" placeholder={t("phone")} name="tel" />
36+
<Input
37+
type="tel"
38+
placeholder={t("phone")}
39+
name="phone"
40+
/>
3641
</div>
3742
<div class="formTopSectionInput">
3843
<Input type="text" placeholder={t("subject")} name="subject" />
3944
</div>
4045
</div>
4146
<div class="formBottomSection">
42-
<Input type="email" placeholder={t("email")} name="email" />
47+
<Input
48+
type="email"
49+
placeholder={t("email")}
50+
name="email"
51+
/>
4352
<Input type="text" placeholder={t("company")} name="company" />
44-
<Input type="text" placeholder={t("content")} name="content" />
53+
<Textarea placeholder={t("content")} name="content" />
4554
</div>
4655
</div>
56+
<div class="errorMessageContainer">
57+
<span id="errorMessage"></span>
58+
</div>
4759
<div class="formButtonContainer">
48-
<Button variant="primary" id="formSubmitButton">{t("send")}</Button>
60+
<Button variant="primary" type="submit" id="formSubmitButton">{t("send")}</Button>
4961
</div>
5062
</form>
5163
</div>
@@ -65,6 +77,35 @@ const t = useTranslations(lang);
6577
</ul>
6678
</div>
6779

80+
<script>
81+
import { type FormFields} from "../types.ts";
82+
import { sendTicket } from "../scripts/api.ts";
83+
import { validateContactForm } from "../scripts/helpers.ts"
84+
85+
const form = document.getElementById("form") as HTMLFormElement;
86+
const errorMessageSpan = document.getElementById("errorMessage");
87+
88+
form?.addEventListener("submit", async (e: SubmitEvent) => {
89+
e.preventDefault();
90+
91+
const formData = new FormData(form);
92+
const data = Object.fromEntries(formData.entries()) as unknown as FormFields;
93+
console.log(data);
94+
const validation = validateContactForm(data);
95+
96+
if(!validation && errorMessageSpan != null){
97+
errorMessageSpan.innerText = "Please ensure all required fields are filled out correctly before submitting.";
98+
return;
99+
}
100+
101+
const response = await sendTicket(data);
102+
103+
// if response is positive, change the view to "sent form view"
104+
})
105+
106+
107+
</script>
108+
68109
<style>
69110
.wrapper{
70111
width: 900px;
@@ -85,6 +126,19 @@ const t = useTranslations(lang);
85126
margin-top: 70px;
86127
}
87128

129+
.errorMessageContainer{
130+
width: 100%;
131+
display: flex;
132+
align-items: center;
133+
justify-content: center;
134+
}
135+
136+
#errorMessage{
137+
color: red;
138+
width: 100%;
139+
padding: 0px 20px;
140+
}
141+
88142
.infoWrapper{
89143
display: flex;
90144
flex-direction: column;
@@ -118,6 +172,11 @@ const t = useTranslations(lang);
118172
top: 10%;
119173
opacity: 0.25;
120174
filter: blur(2px);
175+
z-index: -1;
176+
}
177+
178+
.infoWrapper, .formWrapper, .contactWrapper{
179+
z-index: 1;
121180
}
122181

123182
.formWrapper{

src/sections/Icons.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const contentData: (IconItem | ImageItem)[] = [
7070
) : (
7171
""
7272
)
73-
}
73+
)}
7474
</div>
7575
<div class="item-wrapper">
7676
{contentData.map((item, index) =>

src/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface FormFields{
2+
name: string,
3+
surname: string,
4+
phone: string,
5+
subject: string,
6+
email: string,
7+
company: string,
8+
content: string,
9+
}

0 commit comments

Comments
 (0)