Skip to content

Commit

Permalink
add contact dialog submission functionality with firestore
Browse files Browse the repository at this point in the history
  • Loading branch information
arbisyarifudin committed Mar 12, 2024
1 parent e848cb8 commit e0ca979
Show file tree
Hide file tree
Showing 9 changed files with 834 additions and 39 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
S_API_URL=https://localhost:5000
S_API_KEY=GX12345
203 changes: 172 additions & 31 deletions components/ContactDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,67 @@
<div class="modal-body">
<h1 class="modal-title" id="contactDialogLabel">Contact me</h1>
<p>Send me a message and explain your project. I'll get back to you as soon as possible.</p>

<div class="mb-3">
<label for="sender_name" class="col-form-label d-none">Your Name:</label>
<input type="text" class="form-control" id="sender_name" name="sender_name"
placeholder="Enter your name" required v-model="form.name" @change="errors.name = ''" />
<div class="form-error" v-if="errors?.name?.length">{{ errors.name }}</div>
</div>
<div class="mb-3">
<label for="sender_name" class="col-form-label">Your Name:</label>
<input type="text" class="form-control" id="sender_name" placeholder="Enter your name"
required v-model="form.name" />
<label for="sender_email" class="col-form-label d-none">Your Email:</label>
<input class="form-control" id="sender_email" name="sender_email"
placeholder="Enter your email" required v-model="form.email"
@change="errors.email = ''" />
<div class="form-error" v-if="errors?.email?.length">{{ errors.email }}</div>
</div>
<div class="mb-3">
<label for="sender_email" class="col-form-label">Your Email:</label>
<input class="form-control" id="sender_email" placeholder="Enter your email" required
v-model="form.email" />
<label for="choose_service" class="col-form-label d-none">Choose Service:</label>
<select class="form-select" id="choose_service" v-model="form.service" required
@change="errors.service = ''">
<option value="">- Choose Service -</option>
<option value="Custom Development">Custom Development</option>
<option value="Web Development">Web Development</option>
<option value="Mobile Development">Mobile Development</option>
<option value="Web Design & UI/UX">Web Design & UI/UX</option>
<option value="Tech Consultation">Tech Consultation</option>
<option value="Private Tutoring">Private Tutoring</option>
<option value="Other">Other</option>
</select>
<div class="form-error" v-if="errors?.service?.length">{{ errors.service }}</div>
</div>
<div class="mb-3">
<label for="message" class="col-form-label">Message:</label>
<textarea class="form-control" id="message" placeholder="Enter your message" rows="4"
required v-model="form.message"></textarea>
<label for="message" class="col-form-label d-none">Project Details:</label>
<textarea class="form-control" id="message" name="message"
placeholder="Enter your project details here . ." rows="4" required
v-model="form.message" @change="errors.message = ''"></textarea>
<div class="form-error" v-if="errors?.message?.length">{{ errors.message }}</div>
</div>
<div class="mb-3">
<label for="choose_budget" class="col-form-label d-none">Choose Budget:</label>
<select class="form-select" id="choose_budget" v-model="form.budget" required
@change="errors.budget = ''">
<option value="">- Your Budget -</option>
<option value="2000000">Under Rp 2.000.000</option>
<option value="5000000">&gt; Rp 2.000.000 - Rp 5.000.000</option>
<option value="15000000">&gt; Rp 5.000.000 - Rp 15.000.000</option>
<option value="25000000">&gt; Rp 15.000.000 - Rp 25.000.000</option>
<option value="50000000">&gt; Rp 25.000.000 - Rp 50.000.000</option>
<option value="100000000">&gt; Rp 50.000.000 - Rp 100.000.000</option>
<option value="150000000">Above Rp 100.000.000</option>
</select>
<div class="form-error" v-if="errors?.budget?.length">{{ errors.budget }}</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary"><i class="bi bi-send me-2"></i> Send
Message</button>
<button type="submit" class="btn btn-primary" :disabled="loading">
<i class="bi bi-send me-2" v-if="!loading"></i>
<i class="bi bi-hour-glass me-2" v-else></i>
Send Message
</button>
</div>
</form>
Expand All @@ -49,11 +91,30 @@
</template>
<script setup>
import { useToast } from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-bootstrap.css';
import { ref, computed } from 'vue'
import { useNuxtApp } from '#app';
import { getFirestore, collection, addDoc } from "firebase/firestore";
const { $firebaseApp } = useNuxtApp();
const $toast = useToast();
const form = ref({
name: '',
email: '',
message: ''
message: '',
service: '',
budget: ''
})
const errors = ref({
name: '',
email: '',
message: '',
service: '',
budget: ''
})
const messageText = computed(() => {
Expand All @@ -80,26 +141,102 @@ const messageText = computed(() => {
return encodeURIComponent(text)
})
const submitMessage = () => {
const loading = ref(false)
const db = getFirestore($firebaseApp)
const submitMessage = async () => {
console.log('submit message')
fetch('YOUR_FUNCTION_URL', {
// validate form
for (const key in form.value) {
if (Object.hasOwnProperty.call(form.value, key)) {
const element = form.value[key];
if (!element || element?.length < 1) {
errors.value[key] = 'This field is required'
}
}
}
const isError = Object.values(errors.value).some(err => err.length > 0)
if (isError) return
loading.value = true
try {
// Add a new document to the "submissions" collection
const docRef = await addDoc(collection(db, "submissions"), {
name: form.value.name,
email: form.value.email,
message: form.value.message,
service: form.value.service || 'Other',
budget: form.value.budget ? parseFloat(form.value.budget) : 0,
createdAt: new Date()
});
// console.log("Document written with ID: ", docRef.id);
if (docRef.id) {
hitSApi(docRef.id, form.value)
form.value.name = ''
form.value.email = ''
form.value.message = ''
form.value.service = ''
form.value.budget = ''
// hide modal
const btnClose = document.querySelector('#contactDialog .btn-close')
btnClose.click()
$toast.open({
message: 'Your message has been sent.',
type: 'success',
duration: 3000,
position: 'top-right',
})
}
} catch (err) {
console.error("Error adding document: ", err);
$toast.open({
message: 'Failed to send your message. Please try again later.',
type: 'error',
duration: 3000,
position: 'top-right',
})
} finally {
loading.value = false
}
}
const hitSApi = async (docRefId, formData) => {
const data = JSON.parse(JSON.stringify(formData))
return await fetch('/s/outbound/deal/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com',
message: 'Hello, this is a test message.'
}),
dealName: 'New Deal from FS#' + docRefId,
dealAmount: data.budget ? parseFloat(data.budget) : 0,
dealNotes: data.message,
pipelineID: 'CPP65f046040595d',
// pipelineID: 'CPP655b6c6ee14d9',
stageID: 'CST65f046130abef',
// stageID: 'CST655b6c7d3a42e',
contactNumber: 'FS#' + docRefId,
contactName: data.name,
contactEmail: data.email,
dealProducts: [
{
name: (data.service || 'Other')
}
]
})
})
.then(response => response.text())
.then(data => console.log(data))
.catch((error) => {
console.error('Error:', error);
});
.then(response => response.json())
// .then(data => console.log(data))
.catch(err => console.error(err))
}
</script>
Expand Down Expand Up @@ -129,14 +266,18 @@ const submitMessage = () => {
}
form {
label {
display: none;
}
.form-control {
.form-control,
.form-select {
padding: 0.75rem 1rem;
background-color: #1f1f1f;
}
.form-error {
color: #ffae00;
font-size: 14px;
margin-top: 8px;
}
}
.modal-footer {
Expand Down
2 changes: 1 addition & 1 deletion firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
},
"functions": {
"source": ".output/server",
"runtime": "nodejs20"
"runtime": "nodejs18"
}
}
9 changes: 9 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export default defineNuxtConfig({
// }
// }

runtimeConfig: {
sApiUrl: process.env.S_API_URL,
sApiKey: process.env.S_API_KEY,
},

nitro: {
firebase: {
gen: 2,
Expand All @@ -43,5 +48,9 @@ export default defineNuxtConfig({
nodeVersion: '18',
},
},

plugins: [
'~/plugins/firebase',
],

})
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"firebase": "^10.8.1",
"nuxt": "^3.10.3",
"vue": "^3.4.21",
"vue-router": "^4.3.0",
"vue-toast-notification": "^3.1.2",
"vue3-click-away": "^1.2.4"
},
"devDependencies": {
Expand Down
8 changes: 4 additions & 4 deletions pages/service.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<i class="bi bi-pencil"></i>
</div>
<div class="service-item__body">
<h3 class="service-item__title">Web Design</h3>
<h3 class="service-item__title">Web Design & UI/UX</h3>
<p class="service-item__description">I can help you design a website that suits your
needs and preferences.</p>
</div>
Expand Down Expand Up @@ -74,9 +74,9 @@
<i class="bi bi-lightbulb"></i>
</div>
<div class="service-item__body">
<h3 class="service-item__title">Consultation</h3>
<p class="service-item__description">I can help you with your project by providing
consultation.</p>
<h3 class="service-item__title">Tech Consultation</h3>
<p class="service-item__description">I can help you with your tech-related problems or
questions.</p>
</div>
</div>
</div>
Expand Down
50 changes: 50 additions & 0 deletions plugins/firebase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { defineNuxtPlugin } from '#app'

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";

// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: "AIzaSyAb89WT0u_qrodvGTxWyVAsuZhuvFKXaWg",
authDomain: "arsyaf-web.firebaseapp.com",
databaseURL: "https://arsyaf-web-default-rtdb.asia-southeast1.firebasedatabase.app",
projectId: "arsyaf-web",
storageBucket: "arsyaf-web.appspot.com",
messagingSenderId: "622646676804",
appId: "1:622646676804:web:6e755427a9dcec7a18db11",
measurementId: "G-H0M2MEC1PP"
};

// Initialize Firebase
// const app = initializeApp(firebaseConfig);

// if (process.env.NODE_ENV === 'production') {
// const analytics = getAnalytics(app);
// if (analytics.isSupported()) {
// console.log('firebase analytics initialized'); }
// }

// export default app;

// Initialize Firebase within a Nuxt plugin by using defineNuxtPlugin
export default defineNuxtPlugin(nuxtApp => {
const app = initializeApp(firebaseConfig);

// Here you might want to export or do something with the Firebase app
// For example, adding the Firebase app to the nuxtApp so it can be accessed globally
nuxtApp.provide('firebaseApp', app);

if (process.env.NODE_ENV === 'production') {
const analytics = getAnalytics(app);
if (analytics.isSupported()) {
console.log('firebase analytics initialized');
}
} else {
console.log('firebase initialized');
}
});
Loading

0 comments on commit e0ca979

Please sign in to comment.