mirror of
https://github.com/gabehf/CostInCoffee.git
synced 2026-03-07 13:38:16 -08:00
Version 1 done
This commit is contained in:
parent
8d130b1e7b
commit
582a50ab19
3 changed files with 215 additions and 42 deletions
43
index.css
43
index.css
|
|
@ -8,6 +8,7 @@
|
|||
--brown-light: #52391e;
|
||||
--blue: #05213f;
|
||||
--red: #EF3E36;
|
||||
--red-dark: #d73831;
|
||||
--green: #08670a;
|
||||
--green-light: #217623;
|
||||
--purple: #750973;
|
||||
|
|
@ -93,7 +94,7 @@ button:hover {
|
|||
color: var(--green);
|
||||
}
|
||||
|
||||
.subscriptions {
|
||||
#subscriptions {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
|
|
@ -109,6 +110,7 @@ button:hover {
|
|||
}
|
||||
|
||||
.no-subs-text {
|
||||
color: var(--text-light);
|
||||
font-style: italic;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
|
|
@ -117,6 +119,10 @@ button:hover {
|
|||
.sub-item-container:nth-child(odd) {
|
||||
background-color: white;
|
||||
}
|
||||
.sub-item-container {
|
||||
overflow-x:hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sub-item {
|
||||
width: 350px;
|
||||
|
|
@ -126,6 +132,21 @@ button:hover {
|
|||
margin: auto;
|
||||
}
|
||||
|
||||
.del-sub-button {
|
||||
background-color: var(--red);
|
||||
color: var(--main-bg);
|
||||
border: none;
|
||||
height: 100%;
|
||||
width: 40px;
|
||||
border-radius: 0px;
|
||||
position: absolute;
|
||||
right: -40px;
|
||||
top: 0px;
|
||||
}
|
||||
.del-sub-button:hover {
|
||||
background-color: var(--red-dark);
|
||||
}
|
||||
|
||||
#add-sub-form * {
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
|
@ -158,6 +179,9 @@ input:focus {
|
|||
#subPrice {
|
||||
width: 55px;
|
||||
}
|
||||
#subName {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.ui-autocomplete {
|
||||
background-color: white;
|
||||
|
|
@ -169,7 +193,7 @@ input:focus {
|
|||
padding: 0px;
|
||||
}
|
||||
.ui-autocomplete li {
|
||||
padding: 10px 25px;
|
||||
padding: 10px 0px 8px 25px;
|
||||
}
|
||||
.ui-autocomplete li:hover {
|
||||
cursor: pointer;
|
||||
|
|
@ -180,3 +204,18 @@ input:focus {
|
|||
.error {
|
||||
border: 2px solid var(--red);
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: var(--text-light);
|
||||
margin-bottom: 25px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
.footer a {
|
||||
font-style: italic;
|
||||
color: inherit;
|
||||
}
|
||||
14
index.html
14
index.html
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Josefin+Sans:ital,wght@0,300;0,400;1,300&family=Orelega+One&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Josefin+Sans:ital,wght@0,300;0,400;1,300;1,400&family=Orelega+One&display=swap');
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
|
@ -21,6 +21,11 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="preface">
|
||||
So often you see in ads that their subscription is just <i>the price of a coffee a week</i>
|
||||
or something similar. So just how many coffees a day are you really spending on all these
|
||||
subscriptions?
|
||||
</div>
|
||||
<div class="header">
|
||||
<h1>Cost in Coffee</h1>
|
||||
<h2>See the true cost of your subscriptions in terms of cups of coffee a day.</h2>
|
||||
|
|
@ -31,7 +36,7 @@
|
|||
<p>cups of coffee a day.</p>
|
||||
<p>That's <span id="cost-display">$0.00</span> of coffee.</p>
|
||||
</div>
|
||||
<div class="subscriptions">
|
||||
<div id="subscriptions">
|
||||
<div id="subscription-header">
|
||||
<p>Your Subscriptions</p>
|
||||
<button type="button" id="addSubButton">Add</button>
|
||||
|
|
@ -47,6 +52,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>©2023 Gabe Farrell</p>
|
||||
<a href="https://github.com/gabehf/CostInCoffee">View the source on GitHub</a>
|
||||
</div>
|
||||
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
194
main.js
194
main.js
|
|
@ -1,4 +1,13 @@
|
|||
/*
|
||||
* TODO:
|
||||
* - (maybe) include coffee price somewhere
|
||||
* - (maybe) coffee price selector
|
||||
*/
|
||||
|
||||
/********* CONSTANTS AND GLOBAL VARS *********/
|
||||
|
||||
// List of subscription services I can think of, for the
|
||||
// form to auto-fill
|
||||
const SubList = [
|
||||
'LinkedIn Premium',
|
||||
'Netflix',
|
||||
|
|
@ -6,31 +15,106 @@ const SubList = [
|
|||
'Crunchyroll',
|
||||
'Youtube Premium',
|
||||
'Skillshare',
|
||||
'Max',
|
||||
'Spotify',
|
||||
'Tidal',
|
||||
'Paramount+',
|
||||
'Peacock',
|
||||
'Disney+',
|
||||
'Amazon Prime',
|
||||
'Audible',
|
||||
'Scribd',
|
||||
'Apple TV+',
|
||||
'Curiosity Stream',
|
||||
'Kindle Unlimited'
|
||||
]
|
||||
|
||||
let subscriptions = []
|
||||
let numSubs = 0
|
||||
const pricePattern = /^(\$)?(\d+(\.\d{1,2})?)$/
|
||||
const avgDaysInMonth = 30.475
|
||||
let costOfCoffee = 2.50
|
||||
let costTotal = 0
|
||||
let addFormOpen = false
|
||||
|
||||
// list of prices to be auto-suggested in the form
|
||||
const PriceList = [
|
||||
'$5',
|
||||
'$10',
|
||||
'$12',
|
||||
'$15',
|
||||
'$20'
|
||||
'$20',
|
||||
'$40',
|
||||
]
|
||||
|
||||
// array of current subscriptions
|
||||
let subscriptions = []
|
||||
// pretty much just for IDing the HTML in the sub list. dunno why its global tbh
|
||||
let numSubs = 0
|
||||
// matches $40.00, 40.00, 40, but not €40, 4.000, 4., 50c, etc.
|
||||
const pricePattern = /^(\$)?(\d+(\.\d{1,2})?)$/
|
||||
// these two are for calculating the cost in coffee
|
||||
const avgDaysInMonth = 30.475
|
||||
let costOfCoffee = 2.50
|
||||
// total cost of all the user's subscriptions
|
||||
let costTotal = 0
|
||||
// for opening/closing form
|
||||
let addFormOpen = false
|
||||
|
||||
/***************** FUNCTION DEFINITIONS **************/
|
||||
|
||||
// rebuilds the sub list after one gets removed
|
||||
// if we dont do this, the IDs in the HTML that we use for removing
|
||||
// items starts to drift from the actual JS array
|
||||
function updateSubList() {
|
||||
numSubs = 0
|
||||
console.log('rebuilding sub list...')
|
||||
for (let sub of subscriptions) {
|
||||
// add subscription to page
|
||||
$('#addSubFormContainer').after(`
|
||||
<div class="sub-item-container" id="subscription-${numSubs}">
|
||||
<div class="sub-item">
|
||||
<p>${sub.name}</p>
|
||||
<p>${formatPrice(sub.price)}</p>
|
||||
</div>
|
||||
</div>`)
|
||||
numSubs++
|
||||
}
|
||||
}
|
||||
|
||||
// handler for form
|
||||
function addSubFormHandler() {
|
||||
if (!validatePrice()) {
|
||||
// disallow submit
|
||||
console.log('nope')
|
||||
return
|
||||
}
|
||||
|
||||
let price = processSubValue()
|
||||
|
||||
addSubscription($('#subName').val(), price)
|
||||
|
||||
subscriptions.push({
|
||||
name: $('#subName').val(),
|
||||
price: price,
|
||||
})
|
||||
localStorage.setItem('subscriptions', JSON.stringify(subscriptions))
|
||||
|
||||
$('#subPrice').val('')
|
||||
$('#subName').val('')
|
||||
|
||||
$('#addSubFormContainer').hide()
|
||||
addFormOpen = !addFormOpen
|
||||
}
|
||||
|
||||
// updates the coffee and price displays
|
||||
function updateDisplay() {
|
||||
// update display
|
||||
let dailyCost = costTotal / avgDaysInMonth
|
||||
$('#cost-display').text(`${formatPrice(dailyCost)}`)
|
||||
$('#coffee-count').text(`${(dailyCost / costOfCoffee).toFixed(2)}`)
|
||||
}
|
||||
|
||||
// handles logic for adding a new subscription
|
||||
// note: not just UI logic, also updating total costs etc.
|
||||
function addSubscription(name, price) {
|
||||
|
||||
// make sure no sub text is hidden
|
||||
$('.no-subs-text').hide()
|
||||
// inc. sub count
|
||||
numSubs++
|
||||
|
||||
// add subscription to page
|
||||
$('#addSubFormContainer').after(`
|
||||
<div class="sub-item-container" id="subscription-${numSubs}">
|
||||
<div class="sub-item">
|
||||
|
|
@ -38,14 +122,36 @@ function addSubscription(name, price) {
|
|||
<p>${formatPrice(price)}</p>
|
||||
</div>
|
||||
</div>`)
|
||||
costTotal += price
|
||||
|
||||
// update display
|
||||
let dailyCost = costTotal / avgDaysInMonth
|
||||
$('#cost-display').text(`${formatPrice(dailyCost)}`)
|
||||
$('#coffee-count').text(`${(dailyCost / costOfCoffee).toFixed(2)}`)
|
||||
// inc. sub count
|
||||
numSubs++
|
||||
|
||||
costTotal += price
|
||||
updateDisplay()
|
||||
}
|
||||
|
||||
// handles logic for removing a subscription
|
||||
// note: not just UI logic, also updating total costs etc.
|
||||
function removeSubscription(index) {
|
||||
// get price from sub array
|
||||
let price = subscriptions[index].price
|
||||
// remove sub from array
|
||||
subscriptions.splice(index, 1)
|
||||
// if no subs are left, show no-sub text
|
||||
if (subscriptions.length == 0) {
|
||||
$('.no-subs-text').show()
|
||||
}
|
||||
// remove sub from localstorage
|
||||
localStorage.setItem('subscriptions', JSON.stringify(subscriptions))
|
||||
// remove sub from ui
|
||||
$(`#subscription-${index}`).remove()
|
||||
// subtract price from totalcost
|
||||
costTotal -= price
|
||||
// update display
|
||||
updateDisplay()
|
||||
}
|
||||
|
||||
// takes sub cost from form and returns it as an actual number
|
||||
function processSubValue() {
|
||||
let priceString = $('#subPrice').val()
|
||||
if (priceString[0] == '$') {
|
||||
|
|
@ -54,10 +160,12 @@ function processSubValue() {
|
|||
return Number(priceString)
|
||||
}
|
||||
|
||||
// takes number and returns it as $X.XX string
|
||||
function formatPrice(num) {
|
||||
return '$' + (Math.round(num * 100) / 100).toFixed(2)
|
||||
}
|
||||
|
||||
// checks cost in form against regex, updates UI for error
|
||||
function validatePrice() {
|
||||
let priceString = $('#subPrice').val()
|
||||
if (!priceString.match(pricePattern)) {
|
||||
|
|
@ -85,8 +193,12 @@ if (localStorage.getItem('subscriptions') != null) {
|
|||
|
||||
$('#addSubButton').click(() => {
|
||||
if (!addFormOpen) {
|
||||
$('#addSubButton').css('background-color', 'var(--brown)')
|
||||
$('#addSubButton').css('color', 'var(--main-bg)')
|
||||
$('#addSubFormContainer').show()
|
||||
} else {
|
||||
$('#addSubButton').css('background-color', '')
|
||||
$('#addSubButton').css('color', '')
|
||||
$('#addSubFormContainer').hide()
|
||||
}
|
||||
addFormOpen = !addFormOpen
|
||||
|
|
@ -95,13 +207,14 @@ $('#addSubButton').click(() => {
|
|||
$('#subName').autocomplete({
|
||||
source: SubList,
|
||||
delay: 0,
|
||||
minLength: 0,
|
||||
autoFocus: true,
|
||||
})
|
||||
|
||||
$('#subPrice').autocomplete({
|
||||
source: PriceList,
|
||||
delay: 0,
|
||||
minLength: 0,
|
||||
autoFocus: true,
|
||||
})
|
||||
|
||||
$('#subPrice').on('focus', () => {
|
||||
|
|
@ -109,25 +222,36 @@ $('#subPrice').on('focus', () => {
|
|||
})
|
||||
|
||||
$('#confirmSubButton').click(() => {
|
||||
if (!validatePrice()) {
|
||||
// disallow submit
|
||||
console.log('nope')
|
||||
return
|
||||
addSubFormHandler()
|
||||
})
|
||||
|
||||
$('#add-sub-form').keypress((e) => {
|
||||
if (e.which == 13) {
|
||||
addSubFormHandler()
|
||||
return false
|
||||
}
|
||||
|
||||
let price = processSubValue()
|
||||
|
||||
addSubscription($('#subName').val(), price)
|
||||
|
||||
subscriptions.push({
|
||||
name: $('#subName').val(),
|
||||
price: price,
|
||||
})
|
||||
localStorage.setItem('subscriptions', JSON.stringify(subscriptions))
|
||||
|
||||
$('#subPrice').val('')
|
||||
$('#subName').val('')
|
||||
|
||||
$('#addSubFormContainer').hide()
|
||||
addFormOpen = !addFormOpen
|
||||
// these two are UI logic for the remove subscription button
|
||||
$('#subscriptions').on('mouseenter', '.sub-item-container', function() {
|
||||
$(this).append(`<button id="${$(this).attr('id') + '-del'}" class="del-sub-button">X</button>`);
|
||||
$(`#${$(this).attr('id') + '-del'}`).animate({
|
||||
left: "-=40",
|
||||
}, 200, function() {
|
||||
})
|
||||
})
|
||||
$('#subscriptions').on('mouseleave', '.sub-item-container', function() {
|
||||
$(`#${$(this).attr('id') + '-del'}`).animate({
|
||||
left: "+=40",
|
||||
}, 200, function() {
|
||||
$(`#${$(this).attr('id') + '-del'}`).remove()
|
||||
})
|
||||
})
|
||||
|
||||
$('#subscriptions').on('click', '.del-sub-button', function() {
|
||||
// get index of sub to delete
|
||||
let subIndex = $(this).attr('id').split('-')[1]
|
||||
removeSubscription(subIndex)
|
||||
$('.sub-item-container').remove()
|
||||
updateSubList()
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue