Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
node_modules
package-lock.json
.env
.env
key.env
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ This repository is for the Integrating With HubSpot I: Foundations course. This

To read the full directions, please go to the [practicum instructions](https://app.hubspot.com/academy/l/tracks/1092124/1093824/5493?language=en).

**Put your HubSpot developer test account custom objects URL link here:** https://app.hubspot.com/contacts/l/objects/${custom-obj-number}/views/all/list

**Put your HubSpot developer test account custom objects URL link here:** https://app.hubspot.com/contacts/49606747/objects/2-42537157/views/all/list
___
## Tips:
- Commit to your repository often. Even if you make small tweaks to your code, it’s best to be committing to your repository frequently.
Expand Down
107 changes: 60 additions & 47 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,71 +1,84 @@
const express = require('express');
const axios = require('axios');
const dotenv = require('dotenv');

dotenv.config({ path: 'key.env' }); // Load environment variables

const app = express();
const PORT = 3000;

app.set('view engine', 'pug');
app.use(express.static(__dirname + '/public'));
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

// * Please DO NOT INCLUDE the private app access token in your repo. Don't do this practicum in your normal account.
const PRIVATE_APP_ACCESS = '';

// TODO: ROUTE 1 - Create a new app.get route for the homepage to call your custom object data. Pass this data along to the front-end and create a new pug template in the views folder.

// * Code for Route 1 goes here
// * Ensure the private app access token is stored securely
const PRIVATE_APP_ACCESS = process.env.HUBSPOT_ACCESS_TOKEN;

// TODO: ROUTE 2 - Create a new app.get route for the form to create or update new custom object data. Send this data along in the next route.
// TODO: ROUTE 1 - Fetch and display custom object data on the homepage
app.get('/', async (req, res) => {
try {
const url = 'https://api.hubapi.com/crm/v3/objects/planets?properties=name,colour,size';
const headers = {
Authorization: `Bearer ${PRIVATE_APP_ACCESS}`,
'Content-Type': 'application/json'
};

// * Code for Route 2 goes here
const response = await axios.get(url, { headers });

// TODO: ROUTE 3 - Create a new app.post route for the custom objects form to create or update your custom object data. Once executed, redirect the user to the homepage.
// Log the full API response for debugging
console.log('API Response:', response.data);

// * Code for Route 3 goes here
// Log just the properties field for each record
response.data.results.forEach(record => {
console.log('Record properties:', record.properties);
});

/**
* * This is sample code to give you a reference for how you should structure your calls.
// properties are within 'properties.name', 'properties.colour', and 'properties.size'
const data = response.data.results;

* * App.get sample
app.get('/contacts', async (req, res) => {
const contacts = 'https://api.hubspot.com/crm/v3/objects/contacts';
const headers = {
Authorization: `Bearer ${PRIVATE_APP_ACCESS}`,
'Content-Type': 'application/json'
}
try {
const resp = await axios.get(contacts, { headers });
const data = resp.data.results;
res.render('contacts', { title: 'Contacts | HubSpot APIs', data });
// Render the homepage with the retrieved records
res.render('homepage', { title: 'Planets | HubSpot APIs', records: data });
} catch (error) {
console.error(error);
console.error('Error fetching records:', error);
res.status(500).send('Error retrieving records');
}
});

* * App.post sample
app.post('/update', async (req, res) => {
const update = {
properties: {
"favorite_book": req.body.newVal
}
}
// TODO: ROUTE 2 - Serve the form to create or update custom object data
app.get('/update-coi', (req, res) => {
res.render('update-form', { title: 'Update Custom Object' });
});

const email = req.query.email;
const updateContact = `https://api.hubapi.com/crm/v3/objects/contacts/${email}?idProperty=email`;
const headers = {
Authorization: `Bearer ${PRIVATE_APP_ACCESS}`,
'Content-Type': 'application/json'
};

try {
await axios.patch(updateContact, update, { headers } );
res.redirect('back');
} catch(err) {
console.error(err);
// TODO: ROUTE 3 - Handle form submission to create/update custom objects
app.post('/update-coi', async (req, res) => {
try {
// Get form data
const { name, size, colour } = req.body;

const url = 'https://api.hubapi.com/crm/v3/objects/planets?properties=name,colour,size';
const headers = {
Authorization: `Bearer ${PRIVATE_APP_ACCESS}`,
'Content-Type': 'application/json'
};

// Prepare payload
const data = {
properties: {
name,
size,
colour
}
};

await axios.post(url, data, { headers });

res.redirect('/');
} catch (error) {
console.error('Error creating record:', error.response ? error.response.data : error.message);
res.status(500).send('Error saving record');
}

});
*/


// * Localhost
app.listen(3000, () => console.log('Listening on http://localhost:3000'));
app.listen(PORT, () => console.log(`Listening on http://localhost:${PORT}`));
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
"author": "HubSpot Academy learner",
"license": "ISC",
"dependencies": {
"axios": "^1.3.5",
"express": "^4.18.2",
"axios": "^1.8.4",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"pug": "^3.0.2"
}
}
149 changes: 109 additions & 40 deletions public/css/style.css
Original file line number Diff line number Diff line change
@@ -1,58 +1,127 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
/* Apply a simple reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* Body styling */
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column; /* Stack elements vertically */
justify-content: center; /* Center the content vertically */
align-items: center; /* Center the content horizontally */
min-height: 100vh;
background-color: #f0f0f0; /* Light background color */
padding-top: 20px; /* Add some padding at the top */
}

body, * {
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 0;
/* Header styling */
header {
text-align: center;
width: 100%;
margin-bottom: 20px; /* Adds space between the header and the form */
}

h1 {
margin: 1rem;
font-size: 32px;
color: #333;
margin: 0;
}

.cards {
display: flex;
align-items: center;
justify-content: space-evenly;
flex-wrap: wrap;
/* Form container styling */
form {
background-color: #fff; /* White background for the form */
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px; /* Limits the form width */
}

.card {
flex-basis: 31%;
margin: 1rem 0.4rem;
padding: 1rem 0.4rem 2.5rem 0.4rem;
border: solid 1px lightgrey;
border-radius: 15px;
box-shadow: 3px 2px 6px lightgrey;
/* Styling for labels */
label {
font-weight: bold;
margin-bottom: 5px;
}

.card__email {
font-size: 1rem;
/* Styling for input fields */
input[type="text"] {
width: 100%; /* Full width of the container */
padding: 10px;
margin-bottom: 15px; /* Space between the fields */
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}

.form-wrapper {
font-size: 18px;
max-width: 768px;
margin: 2rem auto;
padding: 2rem;
border: solid 1px lightgrey;
border-radius: 15px;
box-shadow: 3px 2px 6px lightgrey;
/* Styling for the submit button */
button[type="submit"] {
width: 100%;
padding: 12px;
background-color: #4CAF50; /* Green color */
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}

label, input {
margin-top: 5px;
display: block;
font-size: inherit;
button[type="submit"]:hover {
background-color: #45a049; /* Darker green when hovered */
}

input[type="text"] {
padding: .25rem;
/* Footer styles */
footer {
margin-top: 20px;
text-align: center;
}

footer a {
text-decoration: none;
color: #007bff;
font-size: 16px;
}

footer a:hover {
text-decoration: underline;
}

/* Custom Object Table Styles */
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}

input[type="submit"] {
background-color: lightgrey;
border: none;
padding: .375rem 1rem;
margin-top: 10px;
}
th, td {
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}

th {
background-color: #f2f2f2;
font-weight: bold;
}

tr:nth-child(even) {
background-color: #f9f9f9;
}

tr:hover {
background-color: #eaeaea;
}

/* Optional: Style the links in the footer differently when displayed on the table page */
footer a {
color: #007bff;
font-size: 16px;
text-decoration: none;
}

footer a:hover {
text-decoration: underline;
}
25 changes: 25 additions & 0 deletions views/homepage.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
doctype html
html
head
title Custom Object Table
link(rel="stylesheet", href="/css/styles.css")
body
header
h1 Custom Object Table
h2 Integrating With HubSpot I Practicum
main

table
thead
tr
th Name
th Colour
th Size
tbody
each record in records
tr
td= record.properties.name
td= record.properties.colour
td= record.properties.size
footer
a(href="/update-coi") Add / Update this table
24 changes: 24 additions & 0 deletions views/update-form.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
doctype html
html
head
title Update Custom Object
link(rel="stylesheet", href="/css/styles.css")
body
header
h1 Update Custom Object Form
h2 Integrating With HubSpot I Practicum
main
form(action="/update-coi" method="POST")
label(for="name") Name:
input(type="text" name="name" id="name" required)

label(for="size") Size:
input(type="text" name="size" id="size" required)

label(for="colour") Colour:
input(type="text" name="colour" id="colour" required)

button(type="submit") Submit

footer
a(href="/") Go back to the table