i-darrshan commited on
Commit
8b105ad
·
1 Parent(s): b4e7e2b

initial push of the update

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +28 -0
  2. Dockerfile +40 -0
  3. README.md +3 -4
  4. backend/config.js +7 -0
  5. backend/config/googleOAuth.js +12 -0
  6. backend/controller/applicantController.js +117 -0
  7. backend/controller/authController.js +51 -0
  8. backend/controller/contactController.js +132 -0
  9. backend/controller/dateAvailability.js +53 -0
  10. backend/controller/demoRequestController.js +210 -0
  11. backend/controller/holidays.js +97 -0
  12. backend/controller/slots.js +88 -0
  13. backend/controller/subscriptionController.js +92 -0
  14. backend/controller/utils.js +57 -0
  15. backend/database/initdb +1 -0
  16. backend/get-oauth.js +94 -0
  17. backend/index.js +72 -0
  18. backend/package-lock.json +0 -0
  19. backend/package.json +49 -0
  20. backend/routes/applicantRoutes.js +13 -0
  21. backend/routes/authRoutes.js +10 -0
  22. backend/routes/availabilityRoutes.js +62 -0
  23. backend/routes/contactRoutes.js +9 -0
  24. backend/routes/demoRequestRoutes.js +10 -0
  25. backend/routes/subscriptionRoutes.js +10 -0
  26. backend/utils/addSubscriber.js +25 -0
  27. backend/utils/contactRequestDB.js +16 -0
  28. backend/utils/createEvent.js +112 -0
  29. backend/utils/demoRequestDB.js +22 -0
  30. backend/utils/jobRequestDB.js +17 -0
  31. backend/utils/queries.js +50 -0
  32. backend/utils/refreshToken.js +41 -0
  33. backend/utils/sendEmail.js +62 -0
  34. backend/utils/setupDB.js +87 -0
  35. frontend/.gitignore +23 -0
  36. frontend/README.md +70 -0
  37. frontend/package-lock.json +0 -0
  38. frontend/package.json +81 -0
  39. frontend/public/index.html +43 -0
  40. frontend/public/logo.png +0 -0
  41. frontend/public/manifest.json +10 -0
  42. frontend/public/robots.txt +3 -0
  43. frontend/src/App.css +0 -0
  44. frontend/src/App.js +54 -0
  45. frontend/src/assets/images/blog-image-1.jpg +0 -0
  46. frontend/src/assets/images/blog-image-2.jpg +0 -0
  47. frontend/src/assets/images/blog-image-3.jpg +0 -0
  48. frontend/src/assets/images/dr_giridharan.jpeg +0 -0
  49. frontend/src/assets/images/dr_s_rajasekaran.jpeg +0 -0
  50. frontend/src/assets/images/dr_thangaraj.jpeg +0 -0
.gitignore ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ignore test-related folders
2
+ allure_report/
3
+ allure-results/
4
+ __test__/
5
+ __mocks__/
6
+ coverage/
7
+ test_report/
8
+
9
+ # Ignore test configuration and setup files
10
+ jest.config.js
11
+ jest.setup.js
12
+ jesthtmlreporter.config.json
13
+ babel.config.js
14
+ postTestSetup.js
15
+ preTestSetup.js
16
+ runE2ETests.js
17
+
18
+ # Ignore environment files
19
+ .env
20
+
21
+ # Ignore database files
22
+ data.db
23
+
24
+ # Ignore video files
25
+ *.mp4
26
+
27
+ # Ignore node_modules
28
+ node_modules
Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Stage 1: Build the React frontend
2
+ FROM node:16 AS build
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Copy the React frontend code
8
+ COPY frontend/package.json frontend/package-lock.json ./frontend/
9
+ RUN cd frontend && npm install
10
+
11
+ # Build the React app (inside the frontend folder)
12
+ COPY frontend ./frontend
13
+ RUN cd frontend && npm run build
14
+
15
+ # Stage 2: Set up the Node.js backend
16
+ FROM node:16
17
+
18
+ # Set the working directory
19
+ WORKDIR /app
20
+
21
+ # Copy backend code
22
+ COPY backend/package.json backend/package-lock.json ./backend/
23
+ RUN cd backend && npm install
24
+
25
+ # Copy the React build files from Stage 1 to the backend/build folder
26
+ COPY --from=build /app/frontend/build ./backend/build
27
+
28
+ # Copy the backend source code
29
+ COPY backend ./backend
30
+
31
+ RUN mkdir -p /app/backend/database && chmod -R 777 /app/backend/database
32
+
33
+
34
+ # Expose the backend's port
35
+ EXPOSE 7860
36
+
37
+ WORKDIR /app/backend
38
+
39
+ # Start the backend server
40
+ CMD [ "node", "./index.js"]
README.md CHANGED
@@ -1,11 +1,10 @@
1
  ---
2
- title: Genomatics React Modularised V3
3
  emoji: 🏃
4
- colorFrom: blue
5
- colorTo: pink
6
  sdk: docker
7
  pinned: false
8
- short_description: Testing
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Geno React Modularised
3
  emoji: 🏃
4
+ colorFrom: pink
5
+ colorTo: red
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
backend/config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ //Flag for using vapi
2
+ const useVapi = false;
3
+
4
+ //Routes
5
+ const useApiPrefix = true; // Set to true to use /api prefix, false for no prefix
6
+
7
+ module.exports = {useVapi, useApiPrefix};
backend/config/googleOAuth.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { google } = require('googleapis');
2
+ require('dotenv').config();
3
+
4
+ // OAuth2 setup with your credentials from .env file
5
+ const CLIENT_ID = process.env.CLIENT_ID;
6
+ const CLIENT_SECRET = process.env.CLIENT_SECRET;
7
+ const REDIRECT_URI = process.env.REDIRECT_URI;
8
+
9
+ // Google OAuth2 client
10
+ const oAuth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
11
+
12
+ module.exports = { oAuth2Client };
backend/controller/applicantController.js ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { checkAndRefreshAccessToken } = require('../utils/refreshToken');
2
+ const { checkExistingJobApplication, insertJobApplication } = require('../utils/jobRequestDB');
3
+ const { sendEmail } = require('../utils/sendEmail');
4
+
5
+ const submitJobApplication = async (req, res) => {
6
+ const { name, email, phone, experience, role, linkedin } = req.body;
7
+ const resume = req.file;
8
+
9
+ console.log('Received job application:', req.body);
10
+ console.log('Received job CV:', req.file);
11
+
12
+ // Basic validation
13
+ if (!name || !email || !phone || !experience || !role || !linkedin || !resume) {
14
+ console.log("Missing required fields");
15
+ return res.status(400).json({ error: 'Please fill in all required fields.' });
16
+ }
17
+
18
+ // Validate email format
19
+ const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
20
+ if (!emailRegex.test(email)) {
21
+ console.log("Invalid email format");
22
+ return res.status(400).json({ error: 'Please enter a valid email address.' });
23
+ }
24
+
25
+ // Refresh access token if needed
26
+ await checkAndRefreshAccessToken();
27
+
28
+ // Check if job request already exists
29
+ const existingRequest = await checkExistingJobApplication(name, email, role);
30
+ if (existingRequest) {
31
+ console.log(`Your Job request for the role ${role} is already in queue`);
32
+ return res.status(400).json({ error: `Your Job request for the role ${role} is already in queue` });
33
+ }
34
+
35
+ const filename = resume.originalname;
36
+
37
+ // Insert application into the database
38
+ await insertJobApplication(name, email, phone, experience, role, linkedin, resume.buffer, filename);
39
+ console.log('Job Application added to DB successfully');
40
+
41
+ try{
42
+ // =============================
43
+ // Email to Applicant
44
+ // =============================
45
+ const applicantSubject = "Job Application Received";
46
+ const applicantHtmlMessage = `
47
+ <div style="font-family: Arial, sans-serif; color: #333;">
48
+ <center><img src="https://drive.google.com/thumbnail?id=17oMmzl_mTNvohvLhSWHbb_XNPfCC8KaO" alt="Genomatics Logo" height="150px"></center>
49
+ <h2 style="color: #0056b3;">Hello ${name},</h2>
50
+ <p>Thank you for submitting your application for the ${role} role at Genomatics. We’ve received your details and will review your qualifications shortly.</p>
51
+ <p><strong>Submitted Details:</strong></p>
52
+ <ul>
53
+ <li><strong>Name:</strong> ${name}</li>
54
+ <li><strong>Email:</strong> ${email}</li>
55
+ <li><strong>Phone:</strong> ${phone}</li>
56
+ <li><strong>Experience:</strong> ${experience} years</li>
57
+ <li><strong>Role:</strong> ${role}</li>
58
+ <li><strong>LinkedIn:</strong> <a href="${linkedin}" target="_blank">${linkedin}</a></li>
59
+ </ul>
60
+ <p>We appreciate your interest and will reach out if there’s a match for the position.</p>
61
+ <p style="margin-top: 20px;">Best regards,</p>
62
+ <p><strong>The Genomatics Hiring Team</strong></p>
63
+
64
+ <hr style="border: 0; height: 1px; background: #ddd; margin: 20px 0;">
65
+ <p style="font-size: 12px; color: #666;">
66
+ This email was sent in response to your job application form submission on the Genomatics platform.
67
+ </p>
68
+
69
+ <!-- Social Media Links Section -->
70
+ <div style="margin-top: 30px; text-align: center; display: flex; justify-content: center; align-items: center;">
71
+ <p>Follow us on social media:</p>
72
+ <a href="https://x.com/" target="_blank" style="margin: 0 10px;">
73
+ <img src="https://uxwing.com/wp-content/themes/uxwing/download/brands-and-social-media/x-social-media-white-icon.png" alt="Twitter" width="20" height="20" style="border-radius: 50%; background-color: #3A61B9; padding: 5px;">
74
+ </a>
75
+ <a href="https://www.linkedin.com/company/genomatics" target="_blank" style="margin: 0 10px;">
76
+ <img src="https://img.icons8.com/?size=100&id=102748&format=png&color=FFFFFF" alt="LinkedIn" width="20" height="20" style="border-radius: 50%; background-color: #3A61B9; padding: 5px;">
77
+ </a>
78
+ </div>
79
+ </div>`;
80
+
81
+ await sendEmail(email, applicantSubject, applicantHtmlMessage);
82
+
83
+ // =============================
84
+ // 🔹 Email to Authorities (HR Team)
85
+ // =============================
86
+ const authorityEmail = process.env.TEAM_MAIL_IDS;
87
+ const authoritySubject = `New Job Application for ${role}`;
88
+ const authorityHtmlMessage = `
89
+ <div style="font-family: Arial, sans-serif; color: #333; line-height: 1.6;">
90
+ <center><img src="https://drive.google.com/thumbnail?id=17oMmzl_mTNvohvLhSWHbb_XNPfCC8KaO" alt="Genomatics Logo" height="150px"></center>
91
+ <h2 style="color: #0056b3;">📢 New Job Application Received</h2>
92
+ <p>Dear Hiring Team,</p>
93
+ <p>We have received a new job application. Below are the applicant's details:</p>
94
+ <ul>
95
+ <li><strong>Name:</strong> ${name}</li>
96
+ <li><strong>Email:</strong> ${email}</li>
97
+ <li><strong>Phone:</strong> ${phone}</li>
98
+ <li><strong>Experience:</strong> ${experience} years</li>
99
+ <li><strong>Role Applied:</strong> ${role}</li>
100
+ <li><strong>LinkedIn:</strong> <a href="${linkedin}" target="_blank">${linkedin}</a></li>
101
+ </ul>
102
+ <p>📎 The applicant has attached their resume for your review.</p>
103
+ <p>Kindly review the application and take the necessary steps.</p>
104
+ <p style="margin-top: 20px;">Best regards,</p>
105
+ <p><strong>Genomatics Hiring System</strong></p>
106
+ </div>`;
107
+
108
+ await sendEmail(authorityEmail, authoritySubject, authorityHtmlMessage, resume, filename);
109
+
110
+ res.status(200).json({ message: 'Your Job Application has been submitted successfully!' });
111
+ }catch (error) {
112
+ console.error('Error sending emails:', error);
113
+ res.status(400).json({ error: 'Unable to send email for the confirmation of submission, but the application was received suvvessfully!' });
114
+ }
115
+ };
116
+
117
+ module.exports = { submitJobApplication };
backend/controller/authController.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const { google } = require('googleapis');
3
+ require('dotenv').config();
4
+
5
+ // import the OAuth2 client
6
+ const { oAuth2Client } = require('../config/googleOAuth');
7
+
8
+ // Controller function for /auth route
9
+ const redirectToGoogleOAuth = (req, res) => {
10
+ const authUrl = oAuth2Client.generateAuthUrl({
11
+ access_type: 'offline', // Request offline access for refresh token
12
+ scope: ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/calendar'], // Scopes
13
+ });
14
+ res.redirect(authUrl); // Redirect to Google's OAuth consent page
15
+ console.log('Redirecting for OAuth consent...');
16
+ };
17
+
18
+ // Controller function to handle OAuth callback
19
+ const handleOAuthCallback = async (req, res) => {
20
+ const code = req.query.code;
21
+
22
+ try {
23
+ const { tokens } = await oAuth2Client.getToken(code); // Exchange code for tokens
24
+ console.log('Received tokens:', tokens); // Log tokens for debugging
25
+
26
+ // Set tokens to the OAuth2 client
27
+ oAuth2Client.setCredentials(tokens);
28
+
29
+ // Save tokens to the .env file
30
+ saveTokensToEnv(tokens);
31
+
32
+ res.send('Authorization successful! You can now send emails.');
33
+ } catch (error) {
34
+ console.error('Error during OAuth callback:', error);
35
+ res.status(500).send('Failed to authenticate with Google');
36
+ }
37
+ };
38
+
39
+ // Helper function to save tokens to .env file
40
+ const saveTokensToEnv = (tokens) => {
41
+ const envVariables = `
42
+ ACCESS_TOKEN=${tokens.access_token}
43
+ REFRESH_TOKEN=${tokens.refresh_token || 'No refresh token available'}
44
+ `;
45
+
46
+ // Append tokens to the .env file
47
+ fs.appendFileSync('.env', envVariables, 'utf8');
48
+ console.log('Tokens saved to .env file.');
49
+ };
50
+
51
+ module.exports = { redirectToGoogleOAuth, handleOAuthCallback };
backend/controller/contactController.js ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { checkAndRefreshAccessToken } = require('../utils/refreshToken');
2
+ const { checkExistingContactRequest, insertContactRequest } = require('../utils/contactRequestDB');
3
+ const fetch = require('node-fetch');
4
+ const { sendEmail } = require('../utils/sendEmail');
5
+
6
+ //Getting Vapi Usage Flag (Whether to use or not)
7
+ const { useVapi} = require("../config");
8
+
9
+ const assistant_url = 'https://api.vapi.ai/call';
10
+
11
+ const submitContactForm = async (req, res) => {
12
+ const { name, email, phone, subject, message } = req.body;
13
+ console.log('Received contact form submission:', req.body);
14
+
15
+ if (!name || !email || !phone || !subject || !message) {
16
+ return res.status(400).send({ error: 'All fields are required.' });
17
+ }
18
+
19
+ await checkAndRefreshAccessToken();
20
+
21
+ const existingRequest = await checkExistingContactRequest(name, email, subject);
22
+ if (existingRequest) {
23
+ console.log("Your contact request with the same subject is in queue");
24
+ return res.status(400).send({ error: 'Your contact request with the same subject is in queue' });
25
+ }
26
+
27
+ await insertContactRequest(name, email, phone, subject, message);
28
+ console.log('Contact request added successfully');
29
+
30
+ try{
31
+ // 🔹 Email to User
32
+ const userEmailSubject = `Contact Form Submission: ${subject}`;
33
+ const userEmailContent = `
34
+ <div style="font-family: Arial, sans-serif; color: #333;">
35
+ <center><img src="https://drive.google.com/thumbnail?id=17oMmzl_mTNvohvLhSWHbb_XNPfCC8KaO" alt="Genomatics Logo" height="150px"></center>
36
+ <h2 style="color: #0056b3;">Hello ${name},</h2>
37
+ <p>Thank you for reaching out to us! We have received your message and will get back to you soon.</p>
38
+
39
+ <p><strong>Your Submitted Information:</strong></p>
40
+ <p><strong>Name:</strong> ${name}</p>
41
+ <p><strong>Email:</strong> ${email}</p>
42
+ <p><strong>Subject:</strong> ${subject}</p>
43
+ <p><strong>Message:</strong></p>
44
+ <p>${message}</p>
45
+
46
+ <p>We appreciate your interest and are excited to help you. If you have any immediate questions, please feel free to reply to this email directly.</p>
47
+
48
+ <p style="margin-top: 20px;">Best regards,</p>
49
+ <p><strong>The Genomatics Team</strong></p>
50
+
51
+ <hr style="border: 0; height: 1px; background: #ddd; margin: 20px 0;">
52
+ <p style="font-size: 12px; color: #666;">
53
+ This email was sent in response to your contact form submission on the Genomatics platform.
54
+ </p>
55
+
56
+ <!-- Social Media Links Section -->
57
+ <div style="margin-top: 30px; text-align: center; display: flex; justify-content: center; align-items: center;">
58
+ <p>Follow us on social media:</p>
59
+ <a href="https://x.com/" target="_blank" style="margin: 0 10px;">
60
+ <img src="https://uxwing.com/wp-content/themes/uxwing/download/brands-and-social-media/x-social-media-white-icon.png" alt="Twitter" width="20" height="20" style="border-radius: 50%; background-color: #3A61B9; padding: 5px;">
61
+ </a>
62
+ <a href="https://www.linkedin.com/company/genomatics" target="_blank" style="margin: 0 10px;">
63
+ <img src="https://img.icons8.com/?size=100&id=102748&format=png&color=FFFFFF" alt="LinkedIn" width="20" height="20" style="border-radius: 50%; background-color: #3A61B9; padding: 5px;">
64
+ </a>
65
+ </div>
66
+ </div>`;
67
+
68
+ await sendEmail(email, userEmailSubject, userEmailContent);
69
+ console.log("Confirmation email sent to user");
70
+
71
+ // 🔹 Email to Team
72
+ const teamEmail = process.env.TEAM_MAIL_IDS;
73
+ const teamEmailSubject = `New Contact Form Submission: ${subject}`;
74
+ const teamEmailContent = `
75
+ <div style="font-family: Arial, sans-serif; color: #333; line-height: 1.6;">
76
+ <center><img src="https://drive.google.com/thumbnail?id=17oMmzl_mTNvohvLhSWHbb_XNPfCC8KaO" alt="Genomatics Logo" height="150px"></center>
77
+ <h2 style="color: #0056b3;">📩 New Contact Request Received</h2>
78
+ <p>Dear Team,</p>
79
+ <p>A new contact form with some general enquiry/request has been submitted. Details are as follows:</p>
80
+ <table style="width: 100%; border-collapse: collapse;">
81
+ <tr><td style="padding: 8px; font-weight: bold;">Name:</td><td style="padding: 8px;">${name}</td></tr>
82
+ <tr><td style="padding: 8px; font-weight: bold;">Email:</td><td style="padding: 8px;">${email}</td></tr>
83
+ <tr><td style="padding: 8px; font-weight: bold;">Phone:</td><td style="padding: 8px;">${phone}</td></tr>
84
+ <tr><td style="padding: 8px; font-weight: bold;">Subject:</td><td style="padding: 8px;">${subject}</td></tr>
85
+ <tr><td style="padding: 8px; font-weight: bold;">Message:</td><td style="padding: 8px;">${message}</td></tr>
86
+ </table>
87
+ <p>Please follow up with the requester accordingly.</p>
88
+ <p style="margin-top: 20px;">Best regards,</p>
89
+ <p><strong>Genomatics System</strong></p>
90
+ </div>`;
91
+
92
+ await sendEmail(teamEmail, teamEmailSubject, teamEmailContent);
93
+ console.log("Notification email sent to team");
94
+
95
+ res.status(200).send({ message: 'Contact message sent successfully!' });
96
+ } catch (error) {
97
+ console.error('Unable to send Contact request confirmation email, however request registered successfully!', error);
98
+ res.status(200).send({ error: 'Unable to send Contact request confirmation email, however request registered successfully!' });
99
+ }
100
+
101
+ // 🔹 Vapi AI Call
102
+ const curr_time = new Date().toLocaleDateString('en-GB').replace(/\//g, '-');
103
+ const postData = {
104
+ "name": `${phone}_${curr_time}_CRC`,
105
+ "assistantId": process.env.CONTACT_ASSISTANT_ID,
106
+ "assistantOverrides": {
107
+ "variableValues": {
108
+ "name": name,
109
+ "subject": subject,
110
+ "comments": message
111
+ }
112
+ },
113
+ "customer": { "number": phone },
114
+ "phoneNumberId": process.env.PHONE_NUMBER_ID
115
+ };
116
+
117
+ if(useVapi){
118
+ fetch(assistant_url, {
119
+ method: 'POST',
120
+ headers: {
121
+ 'Content-Type': 'application/json',
122
+ 'Authorization': `Bearer ${process.env.VAPI_KEY}`
123
+ },
124
+ body: JSON.stringify(postData)
125
+ })
126
+ .then(response => response.json())
127
+ .then(data => console.log('Vapi AI Call Success:', data))
128
+ .catch(error => console.error('Vapi AI Call Error:', error));
129
+ }
130
+ };
131
+
132
+ module.exports = { submitContactForm };
backend/controller/dateAvailability.js ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { runSelectQuery } = require('../utils/queries'); // Assuming db.js handles DB queries
2
+ const {isHoliday} = require("../controller/holidays")
3
+ const { DateTime } = require('luxon');
4
+
5
+ async function checkDateAvailability(date) {
6
+ const checkQuery = `SELECT COUNT(*) AS count FROM demo_requests WHERE demo_date = ?;`;
7
+ const results = await runSelectQuery(checkQuery, [date]);
8
+
9
+ return results.length === 0 || results[0].count < 11;
10
+ }
11
+
12
+ async function getAvailableDates(currentDate) {
13
+ const availableDates = [];
14
+ // Loop until we collect 14 available dates
15
+ while (availableDates.length < 14) {
16
+ // Check if the current date is a holiday in India
17
+ const isHolidayResult = await isHoliday(currentDate.toFormat('yyyy-MM-dd')); // Pass as JS Date
18
+ const isSundayResult = currentDate.weekday === 7; // Sunday in Luxon is 7
19
+ const isSaturdayResult = currentDate.weekday === 6; // Saturday in Luxon is 6
20
+
21
+ // Count which Saturday it is in the month
22
+ let saturdayCount = 0;
23
+ let tempDate = DateTime.local(currentDate.year, currentDate.month, 1);
24
+
25
+ while (tempDate < currentDate) {
26
+ if (tempDate.weekday === 6) { // If it's a Saturday
27
+ saturdayCount++;
28
+ }
29
+ tempDate = tempDate.plus({ days: 1 });
30
+ }
31
+
32
+ const isFirstThirdOrFifthSaturday = isSaturdayResult && [0, 2, 4].includes(saturdayCount);
33
+
34
+ // Check if the date is available
35
+ const isAvailable = await checkDateAvailability(currentDate.toJSDate());
36
+
37
+ // If it's not a holiday, not a Sunday, not a Saturday (if 1st, 3rd, or 5th Saturday), and it's available, add to available dates
38
+ if (!isHolidayResult && !isSundayResult && !isFirstThirdOrFifthSaturday && isAvailable) {
39
+ // Add the date in 'yyyy-MM-dd' format
40
+ availableDates.push(currentDate.toFormat('yyyy-MM-dd'));
41
+ console.log('Added date:', currentDate);
42
+ } else {
43
+ console.log('Date skipped:', currentDate.toISODate());
44
+ }
45
+
46
+ // Move to the next day (still in Asia/Kolkata timezone)
47
+ currentDate = currentDate.plus({ days: 1 });
48
+ }
49
+
50
+ return availableDates;
51
+ }
52
+
53
+ module.exports = { checkDateAvailability, getAvailableDates };
backend/controller/demoRequestController.js ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { checkAndRefreshAccessToken } = require('../utils/refreshToken');
2
+ const { insertDemoRequest, checkExistingDemoRequest } = require('../utils/demoRequestDB');
3
+ const { sendEmail } = require('../utils/sendEmail');
4
+ const { DateTime } = require('luxon');
5
+ const fetch = require('node-fetch');
6
+ const {createGoogleCalendarEvent} = require('../utils/createEvent');
7
+
8
+ //Getting Vapi Usage Flag (Whether to use or not)
9
+ const { useVapi} = require("../config");
10
+
11
+ // The assistant URL for Vapi & other related config
12
+ const assistant_url = 'https://api.vapi.ai/call';
13
+
14
+ const demoRequest = async (req, res) => {
15
+ const { name, email, company, product, demoDate, selectedSlot, phone, additionalComments, timezone } = req.body;
16
+ console.log('Received demo request:', req.body);
17
+
18
+ // Basic validation on the server side
19
+ if (!name || !email || !company || !demoDate || !phone || !product || !selectedSlot) {
20
+ console.log("Missing required fields");
21
+ return res.status(400).send({ error: 'Please fill in all required fields.' });
22
+ }
23
+
24
+ const convertedDemoDate = DateTime.fromISO(demoDate, { zone: 'utc' }) // Parse demoDate as UTC
25
+ .setZone('Asia/Kolkata') // Convert it to Asia/Kolkata time zone
26
+ .toFormat('yyyy-MM-dd'); // Format it to 'yyyy-MM-dd'
27
+
28
+ console.log('Converted demoDate to Asia/Kolkata timezone:', convertedDemoDate);
29
+
30
+ // Step 2: Convert the Asia/Kolkata date to the user's time zone (const, as we're not reassigning it)
31
+ const userDemoDate = DateTime.fromISO(convertedDemoDate, { zone: 'Asia/Kolkata' }) // Parse demoDate as Asia/Kolkata
32
+ .setZone(timezone) // Convert it to user's time zone (e.g., 'America/New_York')
33
+ .toFormat('yyyy-MM-dd'); // Format it to 'yyyy-MM-dd'
34
+
35
+ console.log('Converted demoDate to user\'s timezone:', userDemoDate);
36
+
37
+ // 1. Check if a demo request already exists within the last 15 days
38
+ const existingRequest = await checkExistingDemoRequest(name, email, product, convertedDemoDate, phone);
39
+
40
+ if (existingRequest) {
41
+ console.log("Demo request already made within 15 days");
42
+ return res.status(400).send({
43
+ error: 'You have already requested a demo for this product within 15 days. Our team will get back to you shortly.'
44
+ });
45
+ }
46
+
47
+ const [startTime, endTime] = selectedSlot.split(' (')[0].split(' - ');
48
+ console.log('Selected time slot:', startTime, endTime);
49
+ console.log('User\'s timezone:', timezone);
50
+ // Convert start time and end time to the user's time zone first
51
+ const startUserTime = DateTime.fromFormat(startTime, 'HH:mm', { zone: timezone });
52
+ const endUserTime = DateTime.fromFormat(endTime, 'HH:mm', { zone: timezone });
53
+
54
+ // Ensure that the conversion to Asia/Kolkata time zone is successful
55
+ const startAsiaKolkata = startUserTime.setZone('Asia/Kolkata').toFormat('HH:mm');
56
+ const endAsiaKolkata = endUserTime.setZone('Asia/Kolkata').toFormat('HH:mm');
57
+
58
+ // Create the formatted time range string
59
+ const formattedRange = `${startAsiaKolkata} - ${endAsiaKolkata}`;
60
+
61
+ console.log('Converted time range to Asia/Kolkata:', formattedRange);
62
+
63
+ // 2. Insert the new demo request into the database
64
+ await insertDemoRequest(name, email, company, product, convertedDemoDate, formattedRange, phone, additionalComments);
65
+ console.log('Demo request added successfully');
66
+
67
+ const [ eventData, icsContent ] = await createGoogleCalendarEvent(name, email, demoDate, selectedSlot, product, timezone);
68
+
69
+ const attachment = {
70
+ buffer: Buffer.from(icsContent, 'utf-8'),
71
+ mimetype: 'text/calendar',
72
+ filename: 'event.ics',
73
+ contentType: 'text/calendar'
74
+ };
75
+
76
+ // // Update the email template with the calendar buttons
77
+ // const calendarButtonsHtml = `
78
+ // <div style="margin: 20px 0; text-align: center;">
79
+ // <div style="background-color:rgb(40, 131, 223); padding: 15px; border-radius: 8px; display: inline-block;">
80
+ // <p style="margin: 0 0 10px 0; font-weight: bold;">Add to Calendar:</p>
81
+ // <table style="border-collapse: separate; border-spacing: 8px; margin: 0 auto;">
82
+ // <tr>
83
+ // <td>
84
+ // <a href="${calendarLinks.google}" target="_blank" style="display: inline-block; padding: 8px 12px; background-color: #4285f4; color: white; text-decoration: none; border-radius: 4px; font-family: Arial, sans-serif; font-size: 14px;">
85
+ // Google Calendar
86
+ // </a>
87
+ // </td>
88
+ // <td>
89
+ // <a href="${calendarLinks.outlook}" target="_blank" style="display: inline-block; padding: 8px 12px; background-color: #0078d4; color: white; text-decoration: none; border-radius: 4px; font-family: Arial, sans-serif; font-size: 14px;">
90
+ // Outlook Calendar
91
+ // </a>
92
+ // </td>
93
+ // <td>
94
+ // <a href="${calendarLinks.yahoo}" target="_blank" style="display: inline-block; padding: 8px 12px; background-color: #720e9e; color: white; text-decoration: none; border-radius: 4px; font-family: Arial, sans-serif; font-size: 14px;">
95
+ // Yahoo Calendar
96
+ // </a>
97
+ // </td>
98
+ // </tr>
99
+ // </table>
100
+ // </div>
101
+ // </div>`;
102
+
103
+ // Refresh access token if needed
104
+ await checkAndRefreshAccessToken();
105
+
106
+ try {
107
+ // 🔹 Email to User
108
+ const userEmailSubject = "Demo Request Confirmation";
109
+ const userEmailContent = `
110
+ <div style="font-family: Arial, sans-serif; color: #333;">
111
+ <center><img src="https://drive.google.com/thumbnail?id=17oMmzl_mTNvohvLhSWHbb_XNPfCC8KaO" alt="Genomatics Logo" height="150px"></center>
112
+ <h2 style="color: #0056b3;">Hello ${name},</h2>
113
+ <p>Thank you for requesting a demo of our <strong>Genomatics</strong> product: <strong>${product}</strong>. We're excited to showcase how our solutions can benefit your team at <strong>${company}</strong>.</p>
114
+ <p><strong>Requested demo date:</strong> ${userDemoDate} - <strong>Time zone:</strong> ${timezone}</p>
115
+ <p><strong>Preferred time slot:</strong> ${selectedSlot}</p>
116
+ <p>We'll be in touch soon to confirm the details. In the meantime, please feel free to reach out with any specific questions or topics you’d like us to cover during the demo.</p>
117
+ <h3 style="color: #0056b3;">Additional Message from You:</h3>
118
+ <p>${additionalComments || "<em>No additional message provided.</em>"}</p>
119
+ <p style="margin-top: 20px;">Best regards,</p>
120
+ <p><strong>The Genomatics Team</strong></p>
121
+
122
+ <hr style="border: 0; height: 1px; background: #ddd; margin: 20px 0;">
123
+ <p style="font-size: 12px; color: #666;">
124
+ This email was sent in response to your contact form submission on the Genomatics platform.
125
+ </p>
126
+
127
+ <!-- Social Media Links Section -->
128
+ <div style="margin-top: 30px; text-align: center; display: flex; justify-content: center; align-items: center;">
129
+ <p>Follow us on social media:</p>
130
+ <a href="https://x.com/" target="_blank" style="margin: 0 10px;">
131
+ <img src="https://uxwing.com/wp-content/themes/uxwing/download/brands-and-social-media/x-social-media-white-icon.png" alt="Twitter" width="20" height="20" style="border-radius: 50%; background-color: #3A61B9; padding: 5px;">
132
+ </a>
133
+ <a href="https://www.linkedin.com/company/genomatics" target="_blank" style="margin: 0 10px;">
134
+ <img src="https://img.icons8.com/?size=100&id=102748&format=png&color=FFFFFF" alt="LinkedIn" width="20" height="20" style="border-radius: 50%; background-color: #3A61B9; padding: 5px;">
135
+ </a>
136
+ </div>
137
+ </div>`;
138
+
139
+ // Send the confirmation email to the user
140
+ await sendEmail(email, userEmailSubject, userEmailContent, attachment, 'event.ics');
141
+ console.log("Confirmation email sent to user");
142
+
143
+ // 🔹 Email to Team
144
+ const teamEmail = process.env.TEAM_MAIL_IDS;
145
+ const teamEmailSubject = `New Demo Request: ${product}`;
146
+ const teamEmailContent = `
147
+ <div style="font-family: Arial, sans-serif; color: #333; line-height: 1.6;">
148
+ <center><img src="https://drive.google.com/thumbnail?id=17oMmzl_mTNvohvLhSWHbb_XNPfCC8KaO" alt="Genomatics Logo" height="150px"></center>
149
+ <h2 style="color: #0056b3;">📩 New Demo Request Received</h2>
150
+ <p>Dear Team,</p>
151
+ <p>A new demo request has been submitted. Details are as follows:</p>
152
+ <table style="width: 100%; border-collapse: collapse;">
153
+ <tr><td style="padding: 8px; font-weight: bold;">Name:</td><td style="padding: 8px;">${name}</td></tr>
154
+ <tr><td style="padding: 8px; font-weight: bold;">Email:</td><td style="padding: 8px;">${email}</td></tr>
155
+ <tr><td style="padding: 8px; font-weight: bold;">Company:</td><td style="padding: 8px;">${company}</td></tr>
156
+ <tr><td style="padding: 8px; font-weight: bold;">Product:</td><td style="padding: 8px;">${product}</td></tr>
157
+ <tr><td style="padding: 8px; font-weight: bold;">Phone:</td><td style="padding: 8px;">${phone}</td></tr>
158
+ <tr><td style="padding: 8px; font-weight: bold;">Demo Date:</td><td style="padding: 8px;">${convertedDemoDate}</td></tr>
159
+ <tr><td style="padding: 8px; font-weight: bold;">Preferred Slot:</td><td style="padding: 8px;">${formattedRange}</td></tr>
160
+ <tr><td style="padding: 8px; font-weight: bold;">Additional Comments:</td><td style="padding: 8px;">${additionalComments || "<em>No additional comments provided.</em>"}</td></tr>
161
+ </table>
162
+ <p>Please follow up with the requester accordingly.</p>
163
+ <p style="margin-top: 20px;">Best regards,</p>
164
+ <p><strong>Genomatics System</strong></p>
165
+ </div>`;
166
+
167
+ // Send the notification email to the team
168
+ await sendEmail(teamEmail, teamEmailSubject, teamEmailContent, attachment, 'event.ics');
169
+ console.log("Notification email sent to team");
170
+
171
+ res.status(200).send({ message: 'Demo request submitted successfully! Our team will get in touch with you soon.' });
172
+ } catch (error) {
173
+ console.error('Unable to send demo request confirmation email, however request registered successfully!', error);
174
+ res.status(200).send({ error: 'Unable to send demo request confirmation email, however the demo request has been registered successfully!' });
175
+ }
176
+
177
+ // 🔹 Vapi AI Call
178
+ const curr_time = DateTime.now().toFormat('yyyy-MM-dd');
179
+ const postData = {
180
+ "name": `${phone}_${curr_time}_DRC`,
181
+ "assistantId": process.env.DEMO_ASSISTANT_ID,
182
+ "assistantOverrides": {
183
+ "variableValues": {
184
+ "name": name,
185
+ "product_name": product,
186
+ "demo_date": demoDate,
187
+ "slot": selectedSlot,
188
+ "comments": additionalComments
189
+ }
190
+ },
191
+ "customer": { "number": phone },
192
+ "phoneNumberId": process.env.PHONE_NUMBER_ID
193
+ };
194
+
195
+ if(useVapi){
196
+ fetch(assistant_url, {
197
+ method: 'POST',
198
+ headers: {
199
+ 'Content-Type': 'application/json',
200
+ 'Authorization': `Bearer ${process.env.VAPI_KEY}`
201
+ },
202
+ body: JSON.stringify(postData)
203
+ })
204
+ .then(response => response.json())
205
+ .then(data => console.log('Vapi AI Call Success:', data))
206
+ .catch(error => console.error('Vapi AI Call Error:', error));
207
+ }
208
+ };
209
+
210
+ module.exports = { demoRequest };
backend/controller/holidays.js ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { google } = require('googleapis');
2
+ const { oAuth2Client } = require('../config/googleOAuth');
3
+ const { checkAndRefreshAccessToken } = require('../utils/refreshToken');
4
+
5
+ let holidays = [];
6
+ let allHolidayDates = [];
7
+
8
+ async function getIndiaTamilHolidays() {
9
+ try {
10
+ const userTokens = await checkAndRefreshAccessToken();
11
+
12
+ // Check if OAuth tokens are available
13
+ if (!userTokens || !userTokens.access_token) {
14
+ console.log("OAuth credentials missing. Please authorize the app first.");
15
+ return { error: 'OAuth credentials missing. Please authorize the app first.' };
16
+ }
17
+
18
+ // Set OAuth2 credentials
19
+ oAuth2Client.setCredentials(userTokens);
20
+
21
+ // Initialize Google Calendar API
22
+ const calendar = google.calendar({ version: 'v3', auth: oAuth2Client });
23
+
24
+ // Define the Indian Holidays calendar ID and time range
25
+ // const calendarId = 'en.indian#[email protected]';
26
+ const calendarId = '[email protected]'; //Tamil Nadu holidays
27
+ const currentYear = new Date().getFullYear(); //works in Indian server only mostly I guess
28
+ const timeMin = `${currentYear}-01-01T00:00:00Z`;
29
+ const timeMax = `${currentYear + 50}-12-31T23:59:59Z`;
30
+
31
+ // Fetch holiday events
32
+ const response = await calendar.events.list({
33
+ calendarId,
34
+ timeMin,
35
+ timeMax,
36
+ singleEvents: true,
37
+ orderBy: 'startTime',
38
+ });
39
+
40
+ // Extract and format holiday details
41
+ const holidays = [];
42
+ response.data.items.forEach(event => {
43
+ const summary = event.summary;
44
+ const startDate = event.start.date;
45
+ const endDate = event.end?.date || event.start.date;
46
+
47
+ // Handle single-day or multi-day holidays
48
+ const start = new Date(startDate);
49
+ const end = new Date(endDate);
50
+
51
+ // Iterate through all dates for multi-day holidays
52
+ for (let d = new Date(start); d < end; d.setDate(d.getDate() + 1)) {
53
+ console.log('Holiday:', summary, d);
54
+ holidays.push({
55
+ festival: summary,
56
+ date: d.toISOString().split('T')[0], // Format date as YYYY-MM-DD
57
+ });
58
+ }
59
+ });
60
+
61
+ return holidays;
62
+ } catch (error) {
63
+ console.error('Error fetching India holidays:', error.message);
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ // Call the function to get tamil holidays
69
+ async function refreshHolidays() {
70
+ try {
71
+ holidays = await getIndiaTamilHolidays(); // Fetch the holidays
72
+ console.log('India Holidays:', holidays);
73
+ console.log('Total holidays:', holidays.length);
74
+ console.log('Last holiday:', holidays[holidays.length - 1]);
75
+
76
+ allHolidayDates = holidays.reduce((accumulator, holiday) => {
77
+ accumulator.push(holiday.date); // Collect all dates in an array
78
+ return accumulator;
79
+ }, []);
80
+
81
+ console.log(allHolidayDates);
82
+
83
+ // After the function finishes, set the next interval
84
+ setTimeout(refreshHolidays, 14 * 24 * 60 * 60 * 1000); // 14 days in ms
85
+ } catch (error) {
86
+ console.error('Failed to fetch holidays:', error);
87
+
88
+ // If there's an error, retry after a short delay
89
+ setTimeout(refreshHolidays, 10000); // Retry in 10 seconds
90
+ }
91
+ }
92
+
93
+ async function isHoliday(date) {
94
+ return allHolidayDates.includes(date);
95
+ }
96
+
97
+ module.exports = { getIndiaTamilHolidays, refreshHolidays, isHoliday };
backend/controller/slots.js ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { runSelectQuery } = require('../utils/queries');
2
+
3
+ const generalSlots = [
4
+ `10:00 - 10:30`, `10:45 - 11:15`, `11:30 - 12:00`, `12:15 - 12:45`, `13:00 - 13:30`,
5
+ `13:45 - 14:15`, `14:30 - 15:00`, `15:15 - 15:45`, `16:00 - 16:30`, `16:45 - 17:15`, `17:30 - 18:00`
6
+ ];
7
+
8
+ //async function to get list of booked slots for a given date
9
+ async function getBookedSlots(date) {
10
+ if (!date) {
11
+ throw new Error('Date is required');
12
+ }
13
+
14
+ // Ensure the date is in the correct format (YYYY-MM-DD)
15
+ const isValidDate = /^\d{4}-\d{2}-\d{2}$/.test(date);
16
+ if (!isValidDate) {
17
+ throw new Error('Invalid date format. Expected YYYY-MM-DD');
18
+ }
19
+
20
+ try {
21
+ const query = `
22
+ SELECT slot FROM demo_requests WHERE demo_date = ?;
23
+ `;
24
+ const results = await runSelectQuery(query, date);
25
+
26
+ console.log(`Fetched ${results.length} slots for date: ${date}`);
27
+
28
+ // Check if results are empty
29
+ if (!results || results.length === 0) {
30
+ console.log(`No slots found for date: ${date}`);
31
+ return []; // Return an empty array if no slots are found
32
+ }
33
+
34
+ // Extract slots, with checks to ensure each result contains a 'slot' property
35
+ const slots = results.map(result => {
36
+ if (!result.slot) {
37
+ console.warn(`No slot found for record: ${JSON.stringify(result)}`);
38
+ return null; // Handle cases where slot is undefined or null
39
+ }
40
+ return result.slot;
41
+ }).filter(slot => slot !== null); // Remove any null values (in case of missing slots)
42
+
43
+ return slots;
44
+ } catch (error) {
45
+ console.error('Error fetching booked slots:', error.message);
46
+ throw error; // Re-throw the error after logging it
47
+ }
48
+ }
49
+
50
+ //async function to get available slots for a given date
51
+ async function getAvailableSlots(date) {
52
+ if (!date) {
53
+ throw new Error('Date is required');
54
+ }
55
+
56
+ // Ensure the date is in the correct format (YYYY-MM-DD)
57
+ const isValidDate = /^\d{4}-\d{2}-\d{2}$/.test(date);
58
+ if (!isValidDate) {
59
+ throw new Error('Invalid date format. Expected YYYY-MM-DD');
60
+ }
61
+
62
+ try {
63
+ // Fetch the booked slots for the given date
64
+ const bookedSlots = await getBookedSlots(date);
65
+ console.log('Booked slots:', bookedSlots);
66
+
67
+ // If no booked slots, all general slots are available
68
+ if (bookedSlots.length === 0) {
69
+ return generalSlots;
70
+ }
71
+
72
+ // Check if generalSlots exists and is an array
73
+ if (!Array.isArray(generalSlots) || generalSlots.length === 0) {
74
+ throw new Error('General slots are not properly defined');
75
+ }
76
+
77
+ // Filter the general slots to remove any booked slots
78
+ const availableSlots = generalSlots.filter(slot => !bookedSlots.includes(slot));
79
+
80
+ // Return the available slots
81
+ return availableSlots;
82
+ } catch (error) {
83
+ console.error('Error fetching available slots:', error.message);
84
+ throw error; // Re-throw the error after logging it
85
+ }
86
+ }
87
+
88
+ module.exports = { getAvailableSlots, getBookedSlots};
backend/controller/subscriptionController.js ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // controllers/subscriptionController.js
2
+ const {oAuth2Client} = require('../config/googleOAuth'); // Import the OAuth2 client
3
+ const { addSubscriber } = require('../utils/addSubscriber'); // Assuming this exists to add the email to the database
4
+ const { checkAndRefreshAccessToken } = require('../utils/refreshToken'); // Import the helper function
5
+ const {google} = require('googleapis');
6
+
7
+ const handleSubscriptionEmail = async (req, res) => {
8
+ const email = req.body.email;
9
+ console.log('Received request to send email to:', email);
10
+
11
+ if (!email) {
12
+ console.log("Email is required");
13
+ return res.status(400).send({ error: 'Email is required' });
14
+ }
15
+
16
+ const result = await addSubscriber(email);
17
+
18
+ if (result.error) {
19
+ // Return error if email already exists or any database error occurs
20
+ return res.status(result.status).send({ error: result.error });
21
+ }
22
+
23
+ await checkAndRefreshAccessToken(); // Refresh the token if expired
24
+
25
+ // Set OAuth2 credentials
26
+ // oAuth2Client.setCredentials(userTokens);
27
+
28
+ // Check if OAuth2 credentials are missing
29
+ if (!oAuth2Client.credentials || !oAuth2Client.credentials.access_token) {
30
+ return res.status(500).send({
31
+ error: 'OAuth credentials missing. Please authorize the app first.'
32
+ });
33
+ }
34
+
35
+ // Prepare the email content
36
+ const subject = "Subscription Confirmation";
37
+ const message = `<div style="font-family: Arial, sans-serif; color: #333;">
38
+ <img src="https://www.genomatics.in/wp-content/uploads/2020/01/cropped-LOGO-1-1.png" alt="Genomatics Logo">
39
+ <h2 style="color: #0056b3;">Hello,</h2>
40
+ <p>Thank you for subscribing to <strong>Genomatics</strong>! We’re excited to have you join our community of genomics enthusiasts, researchers, and professionals. As a valued subscriber, you’ll be among the first to receive updates on breakthrough discoveries, exclusive insights, and expert tips on leveraging genomics for impactful decision-making.</p>
41
+
42
+ <h3 style="color: #0056b3;">What You’ll Get:</h3>
43
+ <ul>
44
+ <li><strong>Exclusive Insights:</strong> Stay ahead with our latest findings on how genomics is shaping the future of medicine, biotechnology, and more.</li>
45
+ <li><strong>Updates on Our Innovations:</strong> Be the first to know about new products and tools designed to empower you in your genomics journey.</li>
46
+ <li><strong>Invitations to Webinars and Events:</strong> Gain access to events and discussions led by leaders in the field to deepen your understanding and application of genomics.</li>
47
+ </ul>
48
+
49
+ <p>If you have any questions or ideas you’d like us to cover, feel free to reach out. We’re here to support you every step of the way.</p>
50
+
51
+ <p>Once again, welcome aboard! We look forward to sharing this journey of discovery with you.</p>
52
+
53
+ <p style="margin-top: 20px;">Warm regards,</p>
54
+ <p><strong>The Genomatics Team</strong></p>
55
+ <hr style="border: 0; height: 1px; background: #ddd; margin: 20px 0;">
56
+ <p style="font-size: 12px; color: #666;">
57
+ This email was sent in response to your subscription to the Genomatics platform.
58
+ </p>
59
+ </div>`;
60
+
61
+ const emailContent = [
62
+ `To: ${email}`,
63
+ 'Content-Type: text/html; charset="UTF-8"',
64
+ 'MIME-Version: 1.0',
65
+ `Subject: ${subject}`,
66
+ '',
67
+ message,
68
+ ].join('\n');
69
+
70
+ // Encode the email content
71
+ const encodedEmail = Buffer.from(emailContent)
72
+ .toString('base64')
73
+ .replace(/\+/g, '-')
74
+ .replace(/\//g, '_');
75
+
76
+ // Send email via Gmail API
77
+ try {
78
+ const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
79
+ const response = await gmail.users.messages.send({
80
+ userId: 'me',
81
+ requestBody: {
82
+ raw: encodedEmail,
83
+ },
84
+ });
85
+ res.status(200).send({ message: 'Subscription email sent!', response });
86
+ } catch (error) {
87
+ console.log('Error sending email:', error);
88
+ res.status(500).send({ error: 'Failed to send subscription confirmation email, however you are subscribed and will receive newsletters' });
89
+ }
90
+ };
91
+
92
+ module.exports = { handleSubscriptionEmail };
backend/controller/utils.js ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { DateTime } = require('luxon');
2
+
3
+ const createDateTimeSlotMapping = (data) => {
4
+ const dateTimeSlots = {};
5
+ data.forEach(dateObj => {
6
+ for (const [date, slots] of Object.entries(dateObj)) {
7
+ slots.forEach(slot => {
8
+ const [startTime, endTime] = slot.split(' - ');
9
+ const dateTime = `${date}T${startTime}`;
10
+
11
+ // Validate the dateTime before adding to the mapping
12
+ const dt = DateTime.fromISO(dateTime);
13
+ if (dt.isValid) {
14
+ dateTimeSlots[dateTime] = `${startTime} - ${endTime}`;
15
+ }
16
+ });
17
+ }
18
+ });
19
+ return dateTimeSlots;
20
+ };
21
+
22
+
23
+ async function slotsInUserZone(inter, timezone) {
24
+ const convertedData = {};
25
+ for (const [key, value] of Object.entries(inter)) {
26
+ const startTimeKolkata = DateTime.fromISO(key, { zone: "Asia/Kolkata" });
27
+ const startTimeUser = startTimeKolkata.setZone(timezone).toISO();
28
+
29
+ const [start, end] = value.split(" - ");
30
+ const startTime = DateTime.fromISO(key, { zone: "Asia/Kolkata" });
31
+ const localStartTime = startTime.setZone(timezone).toFormat("HH:mm");
32
+
33
+ const endTime = startTime.plus({ minutes: 30 });
34
+ const localEndTime = endTime.setZone(timezone).toFormat("HH:mm");
35
+
36
+ convertedData[startTimeUser] = `${localStartTime} - ${localEndTime} (${timezone})`;
37
+ }
38
+ return convertedData;
39
+ }
40
+
41
+ async function getDatesSlots(input) {
42
+ return Object.entries(input).reduce((acc, [timestamp, slot]) => {
43
+ // Validate timestamp
44
+ const dt = DateTime.fromISO(timestamp);
45
+ if (!dt.isValid) {
46
+ return acc; // Skip invalid timestamps
47
+ }
48
+ const date = timestamp.split('T')[0];
49
+ if (!acc[date]) {
50
+ acc[date] = [];
51
+ }
52
+ acc[date].push(slot);
53
+ return acc;
54
+ }, {});
55
+ }
56
+
57
+ module.exports = { createDateTimeSlotMapping, slotsInUserZone, getDatesSlots };
backend/database/initdb ADDED
@@ -0,0 +1 @@
 
 
1
+ //TEST
backend/get-oauth.js ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // index.js
2
+ const express = require('express');
3
+ const { google } = require('googleapis');
4
+ const fs = require('fs');
5
+ require('dotenv').config(); // For loading environment variables
6
+
7
+ const app = express();
8
+ const port = 7860;
9
+
10
+ // Set up OAuth2 client
11
+ const oAuth2Client = new google.auth.OAuth2(
12
+ process.env.CLIENT_ID,
13
+ process.env.CLIENT_SECRET,
14
+ process.env.REDIRECT_URI
15
+ );
16
+
17
+ let userTokens = null; // Store user tokens temporarily in memory
18
+
19
+ // Step 1: Generate the OAuth URL and redirect
20
+ app.get('/auth', (req, res) => {
21
+ const authUrl = oAuth2Client.generateAuthUrl({
22
+ access_type: 'offline', // Request offline access for refresh token
23
+ scope: ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/calendar'], // Gmail send scope
24
+ });
25
+ res.redirect(authUrl); // Redirect to Google's OAuth consent page
26
+ console.log("Redirecting for OAuth consent...");
27
+ });
28
+
29
+ // Step 2: Handle OAuth callback, exchange code for tokens
30
+ app.get('/oauth2callback', async (req, res) => {
31
+ const code = req.query.code;
32
+
33
+ try {
34
+ const { tokens } = await oAuth2Client.getToken(code);
35
+ // console.log('Received tokens:', tokens); // Log tokens for debugging
36
+
37
+ // Store tokens securely (in memory, DB, or .env file)
38
+ oAuth2Client.setCredentials(tokens);
39
+ userTokens = tokens; // Store tokens in memory
40
+
41
+ // Save the tokens to .env file (for future use)
42
+ saveTokensToEnv(tokens);
43
+
44
+ res.send('Authorization successful! You can now send emails.');
45
+ } catch (error) {
46
+ console.error('Error during OAuth callback:', error);
47
+ res.status(500).send('Failed to authenticate with Google');
48
+ }
49
+ });
50
+
51
+ // Helper function to save tokens to .env file
52
+ function saveTokensToEnv(tokens) {
53
+ if (tokens.refresh_token){
54
+ const envVariables = `
55
+ ACCESS_TOKEN=${tokens.access_token}
56
+ REFRESH_TOKEN=${tokens.refresh_token || 'No refresh token available'}
57
+ `;
58
+
59
+ // Save tokens in the .env file
60
+ fs.appendFileSync('.env', envVariables, 'utf8');
61
+ console.log('Tokens saved to .env file.');
62
+ }else{
63
+ const envVariables = `
64
+ ACCESS_TOKEN=${tokens.access_token}
65
+ `;
66
+
67
+ // Save tokens in the .env file
68
+ fs.appendFileSync('.env', envVariables, 'utf8');
69
+ console.log('Tokens saved to .env file.');
70
+ }
71
+
72
+ }
73
+
74
+ // Method to check if the access token is expired
75
+ async function checkAndRefreshAccessToken() {
76
+ if (oAuth2Client.isTokenExpiring()) {
77
+ try {
78
+ // If the access token is expired or about to expire, refresh it
79
+ const response = await oAuth2Client.refreshAccessToken();
80
+ const newTokens = response.credentials;
81
+ oAuth2Client.setCredentials(newTokens); // Set the new credentials
82
+ userTokens = newTokens; // Update the tokens
83
+
84
+ console.log('Access token refreshed.');
85
+ } catch (error) {
86
+ console.error('Error refreshing access token:', error);
87
+ }
88
+ }
89
+ }
90
+
91
+ // Start the Express server
92
+ app.listen(port, () => {
93
+ console.log(`Server running at http://localhost:${port}`);
94
+ });
backend/index.js ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const bodyParser = require('body-parser');
3
+ const cors = require('cors');
4
+ //General
5
+ const path = require('path');
6
+
7
+ //Initialize Database
8
+ const {initializeDatabase} = require("./utils/setupDB");
9
+ initializeDatabase();
10
+
11
+ //Prefix Flag (Use or not)
12
+ const {useApiPrefix} = require('./config')
13
+
14
+ const fs = require('fs');
15
+
16
+ const path1 = '/app/backend/database';
17
+
18
+ if (fs.existsSync(path1)) {
19
+ console.log('The path exists');
20
+ } else {
21
+ console.log('The path does not exist');
22
+ }
23
+
24
+ //Refresh the holidays
25
+ const {refreshHolidays} = require("./controller/holidays");
26
+ refreshHolidays();
27
+
28
+ //Route Imports
29
+ const authRoutes = require('./routes/authRoutes'); // Import the auth routes
30
+ const availabilityRoutes = require('./routes/availabilityRoutes'); // Import availability routes
31
+ const subscriptionRoutes = require('./routes/subscriptionRoutes'); // Import subscription routes
32
+ const demoRequestRoutes = require('./routes/demoRequestRoutes'); // Import demo request routes
33
+ const contactRoutes = require('./routes/contactRoutes'); // Import contact routes
34
+ const applicantRoutes = require('./routes/applicantRoutes'); // Import applicant routes
35
+
36
+ const app = express();
37
+
38
+ app.use(express.json()); // To handle JSON request body
39
+
40
+ // Serve static files from the React build folder
41
+ app.use(express.static(path.join(__dirname, 'build')));
42
+
43
+ // Catch-all route to serve React's index.html for unknown routes
44
+ app.get('*', (req, res) => {
45
+ res.sendFile(path.join(__dirname, 'build', 'index.html'));
46
+ });
47
+
48
+ // Middleware to parse JSON body
49
+ app.use(bodyParser.json());
50
+
51
+ //Use CORS
52
+ app.use(cors());
53
+
54
+ // Routes - Dynamically add /api prefix if the flag is true
55
+ if (useApiPrefix) {
56
+ app.use('/api', authRoutes); // /api/auth
57
+ app.use('/api', subscriptionRoutes); // /api/subscribe
58
+ app.use('/api', availabilityRoutes); // /api/availability
59
+ app.use('/api', demoRequestRoutes); // /api/demo-request
60
+ app.use('/api', contactRoutes); // /api/contact
61
+ app.use('/api', applicantRoutes); // /api/applicant
62
+ } else {
63
+ app.use(authRoutes); // No prefix
64
+ app.use(subscriptionRoutes); // No prefix
65
+ app.use(availabilityRoutes); // No prefix
66
+ app.use(demoRequestRoutes); // No prefix
67
+ app.use(contactRoutes); // No prefix
68
+ app.use(applicantRoutes); // No prefix
69
+ }
70
+
71
+ const PORT = process.env.PORT || 7860;
72
+ app.listen(PORT, '0.0.0.0', () => console.log(`Server running on port: ${PORT}`));
backend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
backend/package.json ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "email-service",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "pretest": "node preTestSetup.js",
8
+ "test": "jest || :",
9
+ "posttest": "node postTestSetup.js"
10
+ },
11
+ "keywords": [],
12
+ "author": "",
13
+ "license": "ISC",
14
+ "dependencies": {
15
+ "@babel/plugin-transform-runtime": "^7.25.9",
16
+ "@babel/preset-env": "^7.26.7",
17
+ "@babel/preset-react": "^7.26.3",
18
+ "body-parser": "^1.20.3",
19
+ "cors": "^2.8.5",
20
+ "dotenv": "^16.4.5",
21
+ "express": "^4.21.1",
22
+ "googleapis": "^144.0.0",
23
+ "ics": "^3.8.1",
24
+ "luxon": "^3.5.0",
25
+ "multer": "^1.4.5-lts.1",
26
+ "mysql2": "^3.11.4",
27
+ "node-fetch": "^2.7.0",
28
+ "sqlite3": "^5.1.7",
29
+ "uuid": "^11.0.5"
30
+ },
31
+ "devDependencies": {
32
+ "allure-commandline": "^2.32.0",
33
+ "allure-jest": "^3.0.9",
34
+ "axios-mock-adapter": "^2.1.0",
35
+ "babel-jest": "^29.7.0",
36
+ "identity-obj-proxy": "^3.0.0",
37
+ "jest": "^29.7.0",
38
+ "jest-allure": "^0.1.3",
39
+ "jest-environment-jsdom": "^29.7.0",
40
+ "jest-html-reporter": "^3.10.2",
41
+ "supertest": "^7.0.0"
42
+ },
43
+ "jest-html-reporter": {
44
+ "pageTitle": "Backend Unit testing",
45
+ "outputPath": "test-report.html",
46
+ "includeFailureMsg": true,
47
+ "executionMode": "reporter"
48
+ }
49
+ }
backend/routes/applicantRoutes.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ // Multer setup to handle file uploads
5
+ const multer = require('multer');
6
+ const storage = multer.memoryStorage();
7
+ const upload = multer({ storage: storage });
8
+
9
+ const {submitJobApplication} = require('../controller/applicantController');
10
+
11
+ router.post('/submit-job-application', upload.single('resume'), submitJobApplication);
12
+
13
+ module.exports = router;
backend/routes/authRoutes.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { redirectToGoogleOAuth, handleOAuthCallback } = require('../controller/authController');
3
+
4
+ const router = express.Router();
5
+
6
+ // Define the /auth routes
7
+ router.get('/auth', redirectToGoogleOAuth);
8
+ router.get('/oauth2callback', handleOAuthCallback);
9
+
10
+ module.exports = router;
backend/routes/availabilityRoutes.js ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const {DateTime} = require('luxon');
3
+ const { getAvailableDates } = require('../controller/dateAvailability');
4
+ const { getAvailableSlots } = require('../controller/slots');
5
+ const { createDateTimeSlotMapping, slotsInUserZone, getDatesSlots } = require('../controller/utils');
6
+ const { isHoliday } = require('../controller/holidays');
7
+
8
+ // This is the part where the route is defined.
9
+ const router = express.Router();
10
+
11
+ router.post('/get-available-dates-and-slots', async (req, res) => {
12
+ const { requiredMinDate, timezone } = req.body;
13
+ console.log('Received request to get available dates:', req.body);
14
+
15
+ if (!requiredMinDate) {
16
+ console.log("Minimum date is required");
17
+ return res.status(400).send({ error: 'Minimum date is required' });
18
+ }
19
+
20
+ try {
21
+ // Convert requiredMinDate to Asia/Kolkata timezone (starting of the day in Asia/Kolkata)
22
+ const currentDate = DateTime.fromISO(requiredMinDate).setZone('Asia/Kolkata').startOf('day');
23
+ console.log('Converted start date in Asia/Kolkata:', currentDate);
24
+
25
+ // Initialize the availableDates array
26
+ const availableDates = await getAvailableDates(currentDate);
27
+
28
+ console.log('Available dates:', availableDates);
29
+ if (availableDates){
30
+ //create an arrow function that creates an array of objects, where each object key is the availableDate and the value is the one returned by getAvailableSlots(availableDate)
31
+ const datesAndSlots = await Promise.all(availableDates.map(async availableDate => {
32
+ const slots = await getAvailableSlots(availableDate);
33
+ return { [availableDate]: slots };
34
+ }));
35
+
36
+ const inter = createDateTimeSlotMapping(datesAndSlots);
37
+ console.log(inter);
38
+
39
+ const userSlots = await slotsInUserZone(inter, timezone);
40
+ console.log(userSlots);
41
+
42
+ const finalDateSlots = await getDatesSlots(userSlots);
43
+ console.log(finalDateSlots);
44
+
45
+ const finalDates = Object.keys(finalDateSlots);
46
+ console.log(finalDates);
47
+
48
+ const response = {
49
+ finalDateSlots: finalDateSlots,
50
+ finalDates: finalDates
51
+ };
52
+ res.json(response);
53
+ }
54
+
55
+ } catch (error) {
56
+ console.error('Error fetching available dates:', error.message);
57
+ res.status(500).send({ error: 'Failed to fetch available dates' });
58
+ }
59
+ });
60
+
61
+ // Export the router so it can be used in the server.js file.
62
+ module.exports = router;
backend/routes/contactRoutes.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const {bodyParser} = require('body-parser');
3
+
4
+ const router = express.Router();
5
+ const {submitContactForm} = require('../controller/contactController');
6
+
7
+ router.post('/submit-contact-form', submitContactForm);
8
+
9
+ module.exports = router;
backend/routes/demoRequestRoutes.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ // routes/subscriptionRoutes.js
2
+ const express = require('express');
3
+ const { demoRequest } = require('../controller/demoRequestController'); // Import controller
4
+
5
+ const router = express.Router();
6
+
7
+ // POST route for subscription email
8
+ router.post('/demo-request', demoRequest);
9
+
10
+ module.exports = router;
backend/routes/subscriptionRoutes.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ // routes/subscriptionRoutes.js
2
+ const express = require('express');
3
+ const { handleSubscriptionEmail } = require('../controller/subscriptionController'); // Import controller
4
+
5
+ const router = express.Router();
6
+
7
+ // POST route for subscription email
8
+ router.post('/subscription-email', handleSubscriptionEmail);
9
+
10
+ module.exports = router;
backend/utils/addSubscriber.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { runQuery, runSelectQuery } = require('./queries');
2
+
3
+ async function addSubscriber(email) {
4
+ try {
5
+ // Check if email exists
6
+ const results = await runSelectQuery('SELECT * FROM subscribers WHERE email = ?', [email]);
7
+ if (results.length > 0) {
8
+ console.log("Email already exists in subscribers");
9
+ return { status: 400, error: 'You have already subscribed!!' };
10
+ }
11
+
12
+ // Insert new subscriber
13
+ await runQuery(
14
+ 'INSERT INTO subscribers (date, time, email) VALUES (DATE("now"), TIME("now"), ?)',
15
+ [email]
16
+ );
17
+ console.log('Subscriber added successfully');
18
+ return { status: 200, message: 'Subscriber added' };
19
+ } catch (error) {
20
+ console.error('Database error:', error);
21
+ return { status: 500, error: 'Failed to add subscriber' };
22
+ }
23
+ }
24
+
25
+ module.exports = { addSubscriber };
backend/utils/contactRequestDB.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const {runQuery, runSelectQuery} = require('./queries');
2
+
3
+ // Check existing contact request
4
+ async function checkExistingContactRequest(name, email, subject) {
5
+ const checkQuery = `SELECT * FROM contact_requests WHERE name = ? AND email = ? AND subject = ?;`;
6
+ const results = await runSelectQuery(checkQuery, [name, email, subject]);
7
+ return results.length > 0;
8
+ }
9
+
10
+ // Insert contact request
11
+ async function insertContactRequest(name, email, phone, subject, message) {
12
+ const insertQuery = `INSERT INTO contact_requests (date, time, name, email, phone, subject, message) VALUES (DATE("now"), TIME("now"), ?, ?, ?, ?, ?);`;
13
+ await runQuery(insertQuery, [name, email, phone, subject, message]);
14
+ }
15
+
16
+ module.exports = { checkExistingContactRequest, insertContactRequest };
backend/utils/createEvent.js ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { checkAndRefreshAccessToken } = require('./refreshToken');
2
+ const { google } = require('googleapis');
3
+ const { oAuth2Client } = require('../config/googleOAuth');
4
+ const { DateTime } = require('luxon');
5
+ const ics = require('ics');
6
+ const { v4: uuid } = require('uuid');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ async function convertToUTC(date, time, timezone) {
11
+ // Combine date and time into one string (e.g., '2025-01-31T14:30')
12
+ const dateTimeString = `${DateTime.fromISO(date).toFormat('yyyy-MM-dd')}T${time}`;
13
+ console.log(dateTimeString);
14
+
15
+ // Parse the combined date-time string in the given timezone
16
+ const dateTimeInTimezone = DateTime.fromISO(dateTimeString, { zone: timezone });
17
+ console.log(dateTimeInTimezone);
18
+
19
+ // Convert it to UTC
20
+ const dateTimeInUTC = dateTimeInTimezone.toUTC();
21
+ console.log(dateTimeInUTC);
22
+
23
+ // Return the UTC time as a string (ISO format)
24
+ return dateTimeInUTC.toISO(); // Returns the date-time in ISO 8601 format (e.g., '2025-01-31T08:00:00.000Z')
25
+ }
26
+
27
+ async function isoToArray(isoString) {
28
+ const dt = DateTime.fromISO(isoString, { zone: 'utc' }); // Ensure it's parsed as UTC
29
+ return [dt.year, dt.month, dt.day, dt.hour, dt.minute];
30
+ }
31
+
32
+
33
+ // Create Google Calendar Event
34
+ async function createGoogleCalendarEvent(name, email, demoDate, selectedSlot, product, timezone) {
35
+
36
+ const [startTime, endTime] = selectedSlot.split(' (')[0].split(' - ');
37
+
38
+ const startDatetime = await convertToUTC(demoDate, startTime, timezone);
39
+ const endDatetime = await convertToUTC(demoDate, endTime, timezone);
40
+
41
+ console.log(`Times: ${startDatetime}, ${endDatetime}`);
42
+
43
+ // Refresh access token if needed
44
+ await checkAndRefreshAccessToken();
45
+
46
+ const calendar = google.calendar({ version: 'v3', auth: oAuth2Client });
47
+
48
+ const event = {
49
+ summary: `Demo for ${product}`,
50
+ description: `Demo for ${product}`,
51
+ start: {
52
+ dateTime: startDatetime, // Use the demo date here
53
+ timeZone: 'UTC',
54
+ },
55
+ end: {
56
+ dateTime: endDatetime, // Assuming the demo lasts 30 minutes
57
+ timeZone: 'UTC',
58
+ },
59
+ colorId: 1,
60
+ conferenceData: {
61
+ createRequest: {
62
+ requestId: uuid(),
63
+ }
64
+ },
65
+ attendees: [
66
+ {email: email},
67
+ ]
68
+ };
69
+
70
+ try {
71
+ const response = await calendar.events.insert({
72
+ calendarId: 'primary',
73
+ conferenceDataVersion: 1,
74
+ resource: event,
75
+ sendUpdates: 'all'
76
+ });
77
+ console.log('Google Calendar Event Created:', response.data);
78
+
79
+ const dateArray = await isoToArray(startDatetime);
80
+ const icsevent = {
81
+ start: dateArray,
82
+ startInputType: 'utc',
83
+ duration: { minutes: 30 },
84
+ title: `Demo for ${product}`,
85
+ description: `Product demonstration for ${product}`,
86
+ url: response.data.hangoutLink,
87
+ categories: ['Product Demo'],
88
+ status: 'CONFIRMED',
89
+ busyStatus: 'BUSY',
90
+ attendees: [
91
+ { name: name, email: email, rsvp: true, partstat: 'ACCEPTED', role: 'REQ-PARTICIPANT' },
92
+ ]
93
+ }
94
+
95
+ const icsFilePath = path.join(__dirname, '../demo-event.ics');
96
+ const icsContent = await new Promise((resolve, reject) => {
97
+ ics.createEvent(icsevent, (error, value) => {
98
+ if (error) {
99
+ reject(error);
100
+ } else {
101
+ resolve(value); // Return .ics content as string
102
+ }
103
+ });
104
+ });
105
+ return [response.data, icsContent];
106
+ } catch (error) {
107
+ console.error('Error creating Google Calendar event:', error);
108
+ throw error;
109
+ }
110
+ }
111
+
112
+ module.exports = {createGoogleCalendarEvent}
backend/utils/demoRequestDB.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const {runSelectQuery, runQuery} = require('./queries');
2
+
3
+ async function checkExistingDemoRequest(name, email, product, demoDate, phone) {
4
+ const checkQuery = `
5
+ SELECT * FROM demo_requests
6
+ WHERE name = ? AND email = ? AND product = ?
7
+ AND ABS(julianday(demo_date) - julianday(?)) <= 15 AND phone = ?;
8
+ `;
9
+ const results = await runSelectQuery(checkQuery, [name, email, product, demoDate, phone]);
10
+ return results.length > 0;
11
+ }
12
+
13
+ // Insert demo request
14
+ async function insertDemoRequest(name, email, company, product, demoDate, slot, phone, message) {
15
+ const insertQuery = `
16
+ INSERT INTO demo_requests (date, time, name, email, company, product, demo_date, slot, phone, comments)
17
+ VALUES (DATE("now"), TIME("now"), ?, ?, ?, ?, ?, ?, ?, ?);
18
+ `;
19
+ await runQuery(insertQuery, [name, email, company, product, demoDate, slot, phone, message]);
20
+ }
21
+
22
+ module.exports = { checkExistingDemoRequest, insertDemoRequest };
backend/utils/jobRequestDB.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const {runQuery, runSelectQuery} = require('./queries');
2
+
3
+ // Check existing job application
4
+ async function checkExistingJobApplication(name, email, role) {
5
+ const checkQuery = `SELECT * FROM applicants WHERE name = ? AND email = ? AND role = ?;`;
6
+ const results = await runSelectQuery(checkQuery, [name, email, role]);
7
+ console.log(results);
8
+ return results.length > 0;
9
+ }
10
+
11
+ // Insert job application
12
+ async function insertJobApplication(name, email, phone, experience, role, linkedin, resume, filename) {
13
+ const insertQuery = `INSERT INTO applicants (date, time, name, email, phone, experience, role, linkedin, resume, filename) VALUES (DATE("now"), TIME("now"), ?, ?, ?, ?, ?, ?, ?, ?);`;
14
+ await runQuery(insertQuery, [name, email, phone, experience, role, linkedin, resume, filename]);
15
+ }
16
+
17
+ module.exports = { checkExistingJobApplication, insertJobApplication };
backend/utils/queries.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //General
2
+ const path = require('path');
3
+
4
+ //SQLite
5
+ const sqlite3 = require('sqlite3').verbose();
6
+
7
+ const fs = require('fs');
8
+
9
+ // Database connection
10
+ console.log(__dirname);
11
+
12
+ const dbFilePath = path.resolve(__dirname,'../database/data.db');
13
+ console.log('The dbpath is', dbFilePath);
14
+
15
+ const db = new sqlite3.Database(dbFilePath, (err) => {
16
+ if (err) {
17
+ console.error('Error connecting to SQLite database:', err.message);
18
+ console.log("Current working directory:", process.cwd());
19
+ } else {
20
+ console.log('Connected to SQLite database');
21
+ }
22
+ });
23
+
24
+ // Helper function to promisify db.run for INSERT, UPDATE, CREATE, etc.
25
+ function runQuery(query, params = []) {
26
+ return new Promise((resolve, reject) => {
27
+ db.run(query, params, function (err) {
28
+ if (err) {
29
+ reject(err);
30
+ } else {
31
+ resolve(this); // `this` contains metadata like `lastID` or `changes`
32
+ }
33
+ });
34
+ });
35
+ }
36
+
37
+ // Helper function to promisify db.all for SELECT queries
38
+ function runSelectQuery(query, params = []) {
39
+ return new Promise((resolve, reject) => {
40
+ db.all(query, params, (err, rows) => {
41
+ if (err) {
42
+ reject(err);
43
+ } else {
44
+ resolve(rows);
45
+ }
46
+ });
47
+ });
48
+ }
49
+
50
+ module.exports = {runQuery, runSelectQuery}
backend/utils/refreshToken.js ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // utils/refreshToken.js
2
+ const { oAuth2Client } = require('../config/googleOAuth'); // Import the OAuth client
3
+
4
+ // Store the user's tokens
5
+ let userTokens = {};
6
+
7
+ // Method to check if the access token is expired and refresh it
8
+ async function checkAndRefreshAccessToken() {
9
+ userTokens = {}; // Reset tokens at start
10
+ if (process.env.ACCESS_TOKEN && process.env.REFRESH_TOKEN) {
11
+ userTokens = {
12
+ access_token: process.env.ACCESS_TOKEN,
13
+ refresh_token: process.env.REFRESH_TOKEN,
14
+ };
15
+ oAuth2Client.setCredentials(userTokens);
16
+ console.log('Using tokens from .env file');
17
+ } else {
18
+ console.log('No tokens found in .env file. Please authenticate.');
19
+ }
20
+
21
+ // console.log(oAuth2Client)
22
+ if (oAuth2Client.isTokenExpiring()) {
23
+ try {
24
+ // Refresh the token if it's expired or about to expire
25
+ const response = await oAuth2Client.refreshAccessToken();
26
+ const newTokens = response.credentials;
27
+ oAuth2Client.setCredentials(newTokens); // Set new credentials
28
+ userTokens = newTokens; // Update tokens
29
+
30
+ process.env.ACCESS_TOKEN = newTokens.access_token
31
+ process.env.REFRESH_TOKEN = newTokens.refresh_token
32
+
33
+ console.log('Access token refreshed.');
34
+ } catch (error) {
35
+ console.error('Error refreshing access token:', error);
36
+ }
37
+ }
38
+ return userTokens
39
+ }
40
+
41
+ module.exports = { checkAndRefreshAccessToken };
backend/utils/sendEmail.js ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { google } = require('googleapis');
2
+ const { oAuth2Client } = require('../config/googleOAuth');
3
+
4
+ const sendEmail = async (recipient, subject, htmlContent, attachment = null, attachmentName = '') => {
5
+ try {
6
+ const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
7
+
8
+ // Unique boundary string
9
+ const boundary = "----=_NextPart_001";
10
+ let rawEmail = [
11
+ `To: ${recipient}`,
12
+ `Subject: ${subject}`,
13
+ `MIME-Version: 1.0`,
14
+ ];
15
+
16
+ if (attachment && attachment.buffer) {
17
+ const encodedAttachment = Buffer.isBuffer(attachment.buffer)
18
+ ? attachment.buffer.toString('base64')
19
+ : Buffer.from(attachment.buffer).toString('base64');
20
+
21
+ const formattedAttachment = encodedAttachment.match(/.{1,76}/g).join("\n");
22
+
23
+ rawEmail.push(
24
+ `Content-Type: multipart/mixed; boundary="${boundary}"`,
25
+ "",
26
+ `--${boundary}`,
27
+ "Content-Type: text/html; charset=UTF-8",
28
+ "",
29
+ htmlContent,
30
+ "",
31
+ `--${boundary}`,
32
+ `Content-Type: ${attachment.mimetype}; name="${attachmentName}"`,
33
+ `Content-Disposition: attachment; filename="${attachmentName}"`,
34
+ "Content-Transfer-Encoding: base64",
35
+ "",
36
+ formattedAttachment,
37
+ "",
38
+ `--${boundary}--`
39
+ );
40
+ } else {
41
+ rawEmail.push("Content-Type: text/html; charset=UTF-8", "", htmlContent);
42
+ }
43
+
44
+ const encodedEmail = Buffer.from(rawEmail.join("\r\n"))
45
+ .toString("base64")
46
+ .replace(/\+/g, "-")
47
+ .replace(/\//g, "_")
48
+ .replace(/=+$/, ""); // Remove padding `=` characters
49
+
50
+ await gmail.users.messages.send({
51
+ userId: "me",
52
+ requestBody: { raw: encodedEmail },
53
+ });
54
+
55
+ console.log(`✅ Email sent successfully to ${recipient}`);
56
+ } catch (error) {
57
+ console.log(`❌ Failed to send email to ${recipient}:`, error);
58
+ throw error;
59
+ }
60
+ };
61
+
62
+ module.exports = { sendEmail };
backend/utils/setupDB.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //SQLite
2
+ const sqlite3 = require('sqlite3').verbose();
3
+ const {runQuery, runSelectQuery} = require("./queries");
4
+
5
+ // Initialize the database by creating tables if they don't exist
6
+ async function initializeDatabase() {
7
+ try {
8
+ // Define the queries for table creation
9
+ const tables = {
10
+ subscribers: `CREATE TABLE IF NOT EXISTS subscribers (
11
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
12
+ date TEXT,
13
+ time TEXT,
14
+ email TEXT NOT NULL UNIQUE
15
+ )`,
16
+ applicants: `CREATE TABLE IF NOT EXISTS applicants (
17
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
18
+ date TEXT,
19
+ time TEXT,
20
+ name TEXT NOT NULL,
21
+ email TEXT NOT NULL,
22
+ phone TEXT,
23
+ role TEXT,
24
+ experience TEXT,
25
+ linkedin TEXT,
26
+ resume BLOB,
27
+ filename TEXT NOT NULL
28
+ )`,
29
+ demo_requests: `CREATE TABLE IF NOT EXISTS demo_requests (
30
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
31
+ date TEXT,
32
+ time TEXT,
33
+ name TEXT NOT NULL,
34
+ email TEXT NOT NULL,
35
+ company TEXT,
36
+ product TEXT,
37
+ demo_date TEXT,
38
+ slot TEXT,
39
+ phone TEXT,
40
+ comments TEXT
41
+ )`,
42
+ contact_requests: `CREATE TABLE IF NOT EXISTS contact_requests (
43
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
44
+ date TEXT,
45
+ time TEXT,
46
+ name TEXT NOT NULL,
47
+ email TEXT NOT NULL,
48
+ phone TEXT,
49
+ subject TEXT,
50
+ message TEXT
51
+ )`,
52
+ purchases: `CREATE TABLE IF NOT EXISTS purchases (
53
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
54
+ purchase_date TEXT,
55
+ time TEXT,
56
+ name TEXT NOT NULL,
57
+ email TEXT NOT NULL,
58
+ phone TEXT,
59
+ product TEXT,
60
+ subscription_plan TEXT,
61
+ subscription_type TEXT
62
+ )`,
63
+ jobs: `CREATE TABLE IF NOT EXISTS jobs (
64
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
65
+ title TEXT NOT NULL,
66
+ description TEXT NOT NULL,
67
+ status TEXT CHECK(status IN ('Open', 'Closed')) NOT NULL DEFAULT 'Open',
68
+ vacancies INTEGER NOT NULL DEFAULT 0,
69
+ job_type TEXT CHECK(job_type IN ('Full-Time', 'Part-Time', 'Contract')) NOT NULL,
70
+ location TEXT CHECK(location IN ('On-Site', 'Remote', 'Hybrid')) NOT NULL,
71
+ date_posted TEXT NOT NULL,
72
+ deadline TEXT,
73
+ image_url TEXT
74
+ )`
75
+ };
76
+
77
+ // Iterate through each table and create it if necessary
78
+ for (const [table, query] of Object.entries(tables)) {
79
+ await runQuery(query);
80
+ console.log(`Table '${table}' is checked/created successfully.`);
81
+ }
82
+ } catch (err) {
83
+ console.error('Database connection error:', err);
84
+ }
85
+ }
86
+
87
+ module.exports = {initializeDatabase}
frontend/.gitignore ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ /coverage
10
+
11
+ # production
12
+ /build
13
+
14
+ # misc
15
+ .DS_Store
16
+ .env.local
17
+ .env.development.local
18
+ .env.test.local
19
+ .env.production.local
20
+
21
+ npm-debug.log*
22
+ yarn-debug.log*
23
+ yarn-error.log*
frontend/README.md ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Getting Started with Create React App
2
+
3
+ This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4
+
5
+ ## Available Scripts
6
+
7
+ In the project directory, you can run:
8
+
9
+ ### `npm start`
10
+
11
+ Runs the app in the development mode.\
12
+ Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13
+
14
+ The page will reload when you make changes.\
15
+ You may also see any lint errors in the console.
16
+
17
+ ### `npm test`
18
+
19
+ Launches the test runner in the interactive watch mode.\
20
+ See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21
+
22
+ ### `npm run build`
23
+
24
+ Builds the app for production to the `build` folder.\
25
+ It correctly bundles React in production mode and optimizes the build for the best performance.
26
+
27
+ The build is minified and the filenames include the hashes.\
28
+ Your app is ready to be deployed!
29
+
30
+ See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31
+
32
+ ### `npm run eject`
33
+
34
+ **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35
+
36
+ If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37
+
38
+ Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39
+
40
+ You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41
+
42
+ ## Learn More
43
+
44
+ You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45
+
46
+ To learn React, check out the [React documentation](https://reactjs.org/).
47
+
48
+ ### Code Splitting
49
+
50
+ This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51
+
52
+ ### Analyzing the Bundle Size
53
+
54
+ This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55
+
56
+ ### Making a Progressive Web App
57
+
58
+ This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59
+
60
+ ### Advanced Configuration
61
+
62
+ This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63
+
64
+ ### Deployment
65
+
66
+ This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67
+
68
+ ### `npm run build` fails to minify
69
+
70
+ This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/package.json ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "@gsap/react": "^2.1.1",
7
+ "@react-icons/all-files": "^4.1.0",
8
+ "@testing-library/jest-dom": "^6.6.3",
9
+ "@testing-library/react": "^16.2.0",
10
+ "@testing-library/user-event": "^14.6.0",
11
+ "axios": "^1.7.9",
12
+ "cra-template": "1.2.0",
13
+ "eslint-config-react-app": "^7.0.1",
14
+ "gsap": "^3.12.5",
15
+ "intl-tel-input": "^25.2.0",
16
+ "luxon": "^3.5.0",
17
+ "react": "^18.0.0",
18
+ "react-datepicker": "^7.6.0",
19
+ "react-day-picker": "^9.5.0",
20
+ "react-dom": "^18.3.1",
21
+ "react-icons": "^5.4.0",
22
+ "react-router-dom": "^6.28.2",
23
+ "react-scripts": "5.0.1",
24
+ "react-typed": "^2.0.12",
25
+ "styled-components": "^6.1.13",
26
+ "web-vitals": "^4.2.4"
27
+ },
28
+ "scripts": {
29
+ "start": "react-scripts start",
30
+ "build": "react-scripts build",
31
+ "test": "jest",
32
+ "eject": "react-scripts eject",
33
+ "test:e2e": "node runE2ETests.js"
34
+ },
35
+ "eslintConfig": {
36
+ "extends": [
37
+ "react-app",
38
+ "react-app/jest"
39
+ ]
40
+ },
41
+ "browserslist": {
42
+ "production": [
43
+ ">0.2%",
44
+ "not dead",
45
+ "not op_mini all"
46
+ ],
47
+ "development": [
48
+ "last 1 chrome version",
49
+ "last 1 firefox version",
50
+ "last 1 safari version"
51
+ ]
52
+ },
53
+ "devDependencies": {
54
+ "@babel/core": "^7.26.0",
55
+ "@babel/preset-env": "^7.26.0",
56
+ "@babel/preset-react": "^7.26.3",
57
+ "allure-commandline": "^2.32.0",
58
+ "allure-jest": "^3.0.9",
59
+ "autoprefixer": "^10.4.20",
60
+ "axios-mock-adapter": "^2.1.0",
61
+ "babel-jest": "^29.7.0",
62
+ "chromedriver": "^132.0.2",
63
+ "identity-obj-proxy": "^3.0.0",
64
+ "jest": "^29.7.0",
65
+ "jest-allure": "^0.1.3",
66
+ "jest-environment-jsdom": "^29.7.0",
67
+ "jest-html-reporter": "^3.10.2",
68
+ "jest-stare": "^2.5.2",
69
+ "jest-styled-components": "^7.2.0",
70
+ "postcss": "^8.4.49",
71
+ "selenium-webdriver": "^4.28.1",
72
+ "tailwindcss": "^3.4.17"
73
+ },
74
+ "proxy": "http://localhost:7860",
75
+ "jest-html-reporter": {
76
+ "pageTitle": "Your test suite",
77
+ "outputPath": "test-report.html",
78
+ "includeFailureMsg": true,
79
+ "executionMode": "reporter"
80
+ }
81
+ }
frontend/public/index.html ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="%PUBLIC_URL%/logo.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta name="theme-color" content="#000000" />
8
+ <meta
9
+ name="description"
10
+ content="Web site created using create-react-app"
11
+ />
12
+ <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo.png" />
13
+ <!--
14
+ manifest.json provides metadata used when your web app is installed on a
15
+ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16
+ -->
17
+ <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18
+ <!--
19
+ Notice the use of %PUBLIC_URL% in the tags above.
20
+ It will be replaced with the URL of the `public` folder during the build.
21
+ Only files inside the `public` folder can be referenced from the HTML.
22
+
23
+ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
24
+ work correctly both with client-side routing and a non-root public URL.
25
+ Learn how to configure a non-root public URL by running `npm run build`.
26
+ -->
27
+ <title>Genomatics | Variants Deciphered</title>
28
+ </head>
29
+ <body>
30
+ <noscript>You need to enable JavaScript to run this app.</noscript>
31
+ <div id="root"></div>
32
+ <!--
33
+ This HTML file is a template.
34
+ If you open it directly in the browser, you will see an empty page.
35
+
36
+ You can add webfonts, meta tags, or analytics to this file.
37
+ The build step will place the bundled scripts into the <body> tag.
38
+
39
+ To begin the development, run `npm start` or `yarn start`.
40
+ To create a production bundle, use `npm run build` or `yarn build`.
41
+ -->
42
+ </body>
43
+ </html>
frontend/public/logo.png ADDED
frontend/public/manifest.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "short_name": "React App",
3
+ "name": "Create React App Sample",
4
+ "icons": [
5
+ ],
6
+ "start_url": ".",
7
+ "display": "standalone",
8
+ "theme_color": "#000000",
9
+ "background_color": "#ffffff"
10
+ }
frontend/public/robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
frontend/src/App.css ADDED
File without changes
frontend/src/App.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
3
+ import Header from "./sections/Header";
4
+ import Footer from "./sections/Footer";
5
+ import Home from "./pages/Home";
6
+ import About from "./pages/About";
7
+ import Blogs from "./pages/Blogs";
8
+ import BlogPage from "./pages/Blogpage"; // Import the BlogPage component
9
+ import Jobs from "./pages/Jobs";
10
+ import JobPage from "./pages/Jobpage";
11
+ import VarDiG from "./pages/VarDiG";
12
+ import Contact from "./pages/Contact";
13
+ import PrivacyPolicy from "./pages/Privacy";
14
+
15
+ import ScrollTop from "./components/ScrollTop";
16
+ import TransitionWrapper from "./components/Transition";
17
+
18
+
19
+ function App() {
20
+ return (
21
+ <Router>
22
+ <ScrollTop />
23
+ <TransitionWrapper>
24
+ <div className="flex flex-col min-h-screen">
25
+ <Header />
26
+
27
+ {/* Main content area */}
28
+ <main className="flex-grow">
29
+ <Routes>
30
+ <Route path="/" element={<Home />} />
31
+ <Route path="/vardig" element={<VarDiG />} />
32
+ <Route path="/about" element={<About />} />
33
+ <Route path="/blogs" element={<Blogs />} />
34
+
35
+ {/* Add Dynamic Route for Individual Blog Pages */}
36
+ <Route path="/blog/:slug" element={<BlogPage/>} />
37
+
38
+ <Route path="/jobs" element={<Jobs />} />
39
+ <Route path="/job/:slug" element={<JobPage/>} />
40
+
41
+ <Route path="/contact" element={<Contact />} />
42
+ <Route path="/privacy-policy" element={<PrivacyPolicy />} />
43
+ </Routes>
44
+ </main>
45
+
46
+ {/* Footer will stay at the bottom */}
47
+ <Footer />
48
+ </div>
49
+ </TransitionWrapper>
50
+ </Router>
51
+ );
52
+ }
53
+
54
+ export default App;
frontend/src/assets/images/blog-image-1.jpg ADDED
frontend/src/assets/images/blog-image-2.jpg ADDED
frontend/src/assets/images/blog-image-3.jpg ADDED
frontend/src/assets/images/dr_giridharan.jpeg ADDED
frontend/src/assets/images/dr_s_rajasekaran.jpeg ADDED
frontend/src/assets/images/dr_thangaraj.jpeg ADDED