Skip to content

Commit

Permalink
feat: Add alerts and reminder notification (#8)
Browse files Browse the repository at this point in the history
* feat: Notification component with status and alert

* feat: Add reminders and error messages
  • Loading branch information
novellac authored Apr 22, 2023
1 parent d2307f0 commit 6196e20
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 31 deletions.
131 changes: 101 additions & 30 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,60 @@
<!-- Header -->
<TheHeader />

<!-- Steps -->
<div class="flex flex-col gap-10 sm:gap-20 lg:gap-4 lg:flex-row">
<StepInput v-model:stepText="bio" :stepNumber="1" class="w-full lg:w-1/2">
<template #stepSummary> Tell us about you. </template>
<template #stepDirections>
Write a few sentences about yourself or paste in your existing job
summary.
</template>
</StepInput>

<StepInput
v-model:stepText="jobDescription"
:stepNumber="2"
class="w-full lg:w-1/2"
>
<template #stepSummary> Tell us about the job. </template>
<template #stepDirections>
Please enter the job description and include all the requirements.
</template>
</StepInput>
</div>
<!-- Form errors -->
<BaseNotification
v-show="hasAnyErrors"
role="alert"
class="mb-8 sm:mb-11 lg:mb-20"
>
<template #title>
Oops! Looks like you forgot to tell us something.
</template>

Please fill in the missing information so we can keep things cookin'!
<ul>
<li v-if="errors.bio">Please provide a bio in step 3.</li>
<li v-if="errors.jobDescription">
Please provide a job description in step 4.
</li>
</ul>
</BaseNotification>

<form @submit.prevent="getCompletion" id="pitchForm">
<!-- Steps -->
<div class="flex flex-col gap-10 sm:gap-20 lg:gap-4 lg:flex-row">
<StepInput
:hasError="errors.bio"
name="bio"
v-model:stepText="bio"
:stepNumber="1"
class="w-full lg:w-1/2"
>
<template #stepSummary> Tell us about you. </template>
<template #stepDirections>
Write a few sentences about yourself or paste in your existing job
summary.
</template>
</StepInput>

<StepInput
:hasError="errors.jobDescription"
name="jobDescription"
v-model:stepText="jobDescription"
:stepNumber="2"
class="w-full lg:w-1/2"
>
<template #stepSummary> Tell us about the job. </template>
<template #stepDirections>
Please enter the job description and include all the requirements.
</template>
</StepInput>
</div>
</form>

<!-- Submit -->
<BaseButton
form="pitchForm"
@click="getCompletion"
type="submit"
:isLoading="isLoading"
Expand All @@ -37,13 +67,25 @@
{{ isLoading ? 'Loading...' : 'Generate your pitch' }}
</BaseButton>

<h2 v-if="results.length" class="text-2xl font-bold text-center">
Your generated pitches
</h2>
<!-- Results -->
<div
class="flex flex-col gap-5 sm:gap-9"
v-if="results.length && !isLoading"
>
<h2 class="text-2xl font-bold text-center">Your generated pitches</h2>

<BaseNotification role="status">
<template #title> Don't forget to copy and paste your info! </template>

<template #default>
We don't want you to have to re-type everything and risk getting
carpal tunnel syndrome!
</template>
</BaseNotification>
</div>

<ResultCardLoading v-if="isLoading" class="w-full" />

<!-- Results -->
<ul v-if="results.length">
<ResultCard
v-for="(result, index) in results"
Expand All @@ -60,26 +102,55 @@

<script setup>
import { inject } from '@vercel/analytics'
import { onMounted, ref } from 'vue'
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import TheHeader from './components/TheHeader.vue'
import BaseButton from './components/BaseButton.vue'
import BaseNotification from './components/BaseNotification.vue'
import StepInput from './components/StepInput.vue'
import ResultCardLoading from './components/ResultCardLoading.vue'
import ResultCard from './components/ResultCard.vue'
const jobDescription = ref(
'Ex: A talented vegan chef who likes cooking desserts and has 78 years of experience.'
)
const bio = ref('Ex: A chef with 6 years of experience in cooking steaks.')
const jobDescription = ref('')
const bio = ref('')
const results = ref([])
const isLoading = ref(false)
const hasAnyErrors = computed(() => {
return Object.values(errors).some((item) => item === true)
})
const errors = reactive({
jobDescription: false,
bio: false,
})
const calculateErrors = async () => {
// Reset errors so we can recalculate, since users may have since changed input values.
resetErrors()
// Let DOM update so assistive tech will be re-triggered.
await nextTick()
if (!bio.value) errors.bio = true
if (!jobDescription.value) errors.jobDescription = true
}
const resetErrors = () => {
Object.keys(errors).forEach((field) => {
errors[field] = false
})
}
const getCompletion = async () => {
// Use may have double clicked
if (isLoading.value) return
await calculateErrors()
if (hasAnyErrors.value) return
isLoading.value = true
const response = await fetch('/api/generate', {
Expand Down
48 changes: 48 additions & 0 deletions src/components/BaseNotification.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import BaseNotification from './BaseNotification.vue'

export default {
title: 'Notifications',
component: BaseNotification,
}

const StatusTemplate = (args) => ({
components: { BaseNotification },
setup() {
return { args }
},
template: `
<BaseNotification v-bind="args">
<template #title>
Poke poke, this is a gentle reminder about something shiny.
</template>
In order to get the most out of this app, please adjust your phasers to stun and hold on tight to the handrails.
</BaseNotification>
`,
})

export const Status = StatusTemplate.bind({})
Status.args = {
role: 'status',
}

const AlertTemplate = (args) => ({
components: { BaseNotification },
setup() {
return { args }
},
template: `
<BaseNotification v-bind="args">
<template #title>
Oops! This is an alert which will warn you that things have gone wonky!
</template>
Please do brush your unicorn a little more thorougly, so that your unicorn will keep that glossy mane and coat we've come to know and love.
</BaseNotification>
`,
})

export const Alert = AlertTemplate.bind({})
Alert.args = {
role: 'alert',
}
35 changes: 35 additions & 0 deletions src/components/BaseNotification.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<div :role="role" :class="theme" class="px-4 py-2 border-l-4">
<h2 class="font-bold">
<slot name="title" />
</h2>

<p class="leading-5">
<slot />
</p>
</div>
</template>

<script setup>
import { computed } from 'vue'
const props = defineProps({
role: {
type: String,
default: 'status',
validator(value) {
return ['status', 'alert'].includes(value)
},
},
})
const theme = computed(() => {
switch (props.role) {
case 'status':
default:
return ['bg-blue-100', 'text-blue-700', 'border-blue-500']
case 'alert':
return ['bg-yellow-100', 'text-red-700', 'border-yellow-400']
}
})
</script>
17 changes: 16 additions & 1 deletion src/components/StepInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import BaseLabel from './BaseLabel.vue';
<template>
<div class="flex flex-col justify-between">
<BaseLabel :for="stepId" class="flex">
<span class="mr-9 sm:mr-14 lg:mr-16">
<span
class="mr-9 sm:mr-14 lg:mr-16"
:class="
hasError ? 'p-1 rounded-full outline outline-4 outline-red-700' : ''
"
>
<StepNumber :stepNumber="stepNumber" />
</span>
<span>
Expand All @@ -15,6 +20,8 @@ import BaseLabel from './BaseLabel.vue';
</BaseLabel>

<BaseTextArea
:name="name"
required="true"
:id="stepId"
:modelValue="stepText"
class="h-40 p-2 sm:h-64 lg:h-72"
Expand All @@ -37,6 +44,14 @@ const props = defineProps({
type: String,
default: '',
},
name: {
type: String,
default: '',
},
hasError: {
type: Boolean,
default: false,
},
})
defineEmits(['update:stepText'])
Expand Down

1 comment on commit 6196e20

@vercel
Copy link

@vercel vercel bot commented on 6196e20 Apr 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.