diff --git a/.gitignore b/.gitignore index 06a83675..3072f71b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store node_modules package-lock.json -.env \ No newline at end of file +.env +key.env diff --git a/README.md b/README.md index 76d6d581..1c6b66ac 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/index.js b/index.js index f337a32d..5475627a 100644 --- a/index.js +++ b/index.js @@ -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')); \ No newline at end of file +app.listen(PORT, () => console.log(`Listening on http://localhost:${PORT}`)); diff --git a/package.json b/package.json index 62db37aa..77ea4c2d 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/public/css/style.css b/public/css/style.css index 85587bb4..cc8711cd 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -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; -} \ No newline at end of file +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; +} diff --git a/views/homepage.pug b/views/homepage.pug new file mode 100644 index 00000000..364367e2 --- /dev/null +++ b/views/homepage.pug @@ -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 diff --git a/views/update-form.pug b/views/update-form.pug new file mode 100644 index 00000000..bd13d7d7 --- /dev/null +++ b/views/update-form.pug @@ -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