Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ebanDev committed Nov 10, 2024
0 parents commit b68564c
Show file tree
Hide file tree
Showing 22 changed files with 1,244 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
.output
.nuxt
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<img src="public/favicon.png" alt="ThermoTrack Logo" width="200" style="display: block; margin: 0 auto;">

# ThermoTrack - A heat-based contraception tracking PWA

## Description

ThermoTrack is a heat-based contraception tracking PWA that help users of devices such as [AndroSwitch](https://thoreme.com/en/) to track their contraception status. It is a Progressive Web App (PWA) that can be installed on your device and used offline. It is built with NuxtJS and uses Pinia for state management.
33 changes: 33 additions & 0 deletions app.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<template>
<NuxtLayout>
<NuxtPage/>
</NuxtLayout>
</template>

<style>
.page-enter-active,
.page-leave-active {
transition: all 0.075s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
}
</style>

<script>
let defferedPrompt;
const installPWA = () => {
defferedPrompt.prompt();
defferedPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
defferedPrompt = null;
});
};
</script>
Binary file added bun.lockb
Binary file not shown.
112 changes: 112 additions & 0 deletions components/ProgressCircle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<template>
<div class="progress-circle">
<svg :width="size" :height="size">
<circle
class="progress-ring__background"
:stroke-width="strokeWidth"
:r="normalizedRadius"
:cx="radius"
:cy="radius"
/>
<circle
class="progress-ring__circle"
:stroke-width="strokeWidth"
:stroke-dasharray="circumference + ' ' + circumference"
:stroke-dashoffset="strokeDashoffset"
stroke-linecap="round"
:r="normalizedRadius"
:cx="radius"
:cy="radius"
/>
<circle
v-if="progress > 100"
class="progress-ring__circle--extra"
:stroke-width="strokeWidth"
:stroke-dasharray="circumference + ' ' + circumference"
:stroke-dashoffset="extraStrokeDashoffset"
stroke-linecap="round"
:r="normalizedRadius"
:cx="radius"
:cy="radius"
/>
</svg>
<div v-if="text" class="progress-text">{{ text }} <span>{{ subtext }}</span></div>
</div>
</template>

<script setup>
const props = defineProps({
progress: {
type: Number,
required: true,
default: 0,
},
text: {
type: String,
required: false,
default: '',
},
subtext: {
type: String,
required: false,
default: '',
},
size: {
type: Number,
default: 100,
},
strokeWidth: {
type: Number,
default: 10,
},
});
const radius = computed(() => props.size / 2);
const normalizedRadius = computed(() => radius.value - props.strokeWidth / 2);
const circumference = computed(() => 2 * Math.PI * normalizedRadius.value);
const strokeDashoffset = computed(() => circumference.value - (Math.min(props.progress, 100) / 100) * circumference.value);
const extraStrokeDashoffset = computed(() => circumference.value - ((props.progress - 100) / 100) * circumference.value);
</script>

<style scoped>
.progress-circle {
position: relative;
display: inline-block;
}
svg {
transform: rotate(-90deg);
}
.progress-ring__background {
fill: transparent;
stroke: var(--border-color);
}
.progress-ring__circle {
fill: transparent;
stroke: var(--cta-color);
transition: stroke-dashoffset var(--transition) linear;
}
.progress-ring__circle--extra {
fill: transparent;
stroke: var(--cta-hover-color);
transition: stroke-dashoffset var(--transition) linear;
}
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.75rem;
font-weight: bolder;
color: var(--text-color);
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
span {
font-size: 1.2rem;
font-weight: 600;
text-wrap: nowrap;
}
}
</style>
66 changes: 66 additions & 0 deletions components/TabBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script setup lang="ts">
const currentRoute = useRoute()
</script>

<template>
<nav class="tab-bar">
<div class="item" :class="{ active: currentRoute.path === '/' }">
<nuxt-link to="/">
<Icon name="i-tabler-home"/>
<span>Home</span>
</nuxt-link>
</div>
<div class="item" :class="{ active: currentRoute.path === '/results' }">
<nuxt-link to="/results">
<Icon name="i-tabler-chart-bar"/>
<span>Results</span>
</nuxt-link>
</div>
<div class="item" :class="{ active: currentRoute.path === '/info' }">
<nuxt-link to="/info">
<Icon name="i-tabler-info-circle"/>
<span>Info</span>
</nuxt-link>
</div>
</nav>
</template>

<style scoped>
nav {
display: flex;
justify-content: space-around;
background-color: var(--tabbar-color);
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 10px 0;
.item {
transition: all 0.1s;
}
a {
display: flex;
flex-direction: column;
align-items: center;
text-decoration: none;
color: var(--text-color);
padding: 5px 15px;
border-radius: 15px;
.iconify {
font-size: 26px;
}
span:not(.iconify) {
display: none;
}
}
.item.active a {
color: var(--cta-hover-color);
background: var(--tabbar-active-color);
}
}
</style>
89 changes: 89 additions & 0 deletions components/TopBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<script setup lang="ts">
const route = useRoute()
const showSettings = ref(false);
const { wearingSessions, wearingGoal } = storeToRefs(useUserPrefsStore());
const streak = computed(() => {
const today = new Date();
today.setHours(0, 0, 0, 0);
let currentStreak = 0;
for (let i = 0; i < wearingSessions.value.length; i++) {
console.log(new Date(wearingSessions.value[i].start) >= today);
if (new Date(wearingSessions.value[i].start) >= today) {
currentStreak++;
} else {
break;
}
}
return {
count: currentStreak,
};
});
</script>

<template>
<header>
<h1>
<Icon name="i-tabler-left-fill" v-if="route.meta.showBackButton" @click="$router.back()"/>
{{ route.meta.title }}</h1>
<div class="left">
<span class="streak">
<Icon name="i-tabler-flame" />
{{ streak.count }}
</span>
<button @click="showSettings = true">
<Icon name="i-tabler-settings"/>
</button>
</div>
</header>
<dialogs-settings v-if="showSettings" @close="showSettings = false"/>
</template>

<style scoped>
.streak {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 1.3rem;
font-weight: bold;
background: var(--tabbar-active-color);
padding: 0.25rem 0.75rem;
border-radius: 0.5rem;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1rem;
background: var(--tabbar-color);
z-index: 999;
position: sticky;
top: 0;
width: 100%;
gap: 1rem;
h1 {
font-size: 1.5rem;
font-weight: bold;
display: flex;
align-items: center;
gap: 0.5rem;
}
.left {
display: flex;
align-items: center;
gap: 1rem;
button {
padding: 0.5rem;
border-radius: 2rem;
}
}
}
</style>
Loading

0 comments on commit b68564c

Please sign in to comment.