Skip to content

Commit

Permalink
feat: Allow users to see multiple pitches (#7)
Browse files Browse the repository at this point in the history
* hotfix: ignore vercel dierctory

* feat: Allow user to see multiple pitches

* hotfix: Ensure cards come through as list items

* design: design tweaks from Figma
  • Loading branch information
novellac authored Apr 2, 2023
1 parent ad7bea3 commit d2307f0
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 27 deletions.
65 changes: 43 additions & 22 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,35 @@
</template>
</StepInput>
</div>

<!-- Submit -->
<BaseButton
@click="getCompletion"
type="button"
:disabled="isLoading"
type="submit"
:isLoading="isLoading"
class="w-full"
>
{{ isLoading ? 'Loading...' : 'Generate your pitch' }}
</BaseButton>
<template v-if="result">
<h2 class="my-24 text-2xl font-bold text-center lg:my-36h md:my-20h">
Your generated pitch
</h2>
<p class="p-3 border-4 rounded-2xl">
{{ result }}
</p>
</template>
<template v-if="isLoading">
<h2 class="my-24 text-2xl font-bold text-center lg:my-36h md:my-20h">
Loading good stuff here (real loading spinner to come)...
</h2>
</template>

<h2 v-if="results.length" class="text-2xl font-bold text-center">
Your generated pitches
</h2>

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

<!-- Results -->
<ul v-if="results.length">
<ResultCard
v-for="(result, index) in results"
:key="index"
tag="li"
class="flex flex-col-reverse border-4 border-neutral-200 rounded-2xl"
:time="result.generatedAt"
:text="result.text"
:order="results.length - index"
/>
</ul>
</main>
</template>

Expand All @@ -57,19 +65,22 @@ import { onMounted, ref } from 'vue'
import TheHeader from './components/TheHeader.vue'
import BaseButton from './components/BaseButton.vue'
import StepInput from './components/StepInput.vue'
import ResultCardLoading from './components/ResultCardLoading.vue'
import ResultCard from './components/ResultCard.vue'
const jobDescription = ref(
'A talented vegan chef who likes cooking desserts and has 78 years of experience.'
'Ex: A talented vegan chef who likes cooking desserts and has 78 years of experience.'
)
const bio = ref('A chef with 6 years of experience in cooking steaks.')
const result = ref('')
const bio = ref('Ex: A chef with 6 years of experience in cooking steaks.')
const results = ref([])
const isLoading = ref(false)
const getCompletion = async () => {
isLoading.value = true
if (isLoading.value) return
result.value = ''
isLoading.value = true
const response = await fetch('/api/generate', {
method: 'POST',
Expand All @@ -85,7 +96,6 @@ const getCompletion = async () => {
if (!response.ok) {
throw new Error(response.statusText)
}
const data = response.body
if (!data) {
return
Expand All @@ -95,16 +105,27 @@ const getCompletion = async () => {
const decoder = new TextDecoder()
let done = false
const currentResultText = ref('')
while (!done) {
const { value, done: doneReading } = await reader.read()
done = doneReading
const chunkValue = decoder.decode(value)
result.value += chunkValue
currentResultText.value += chunkValue
}
// Create and add a new pitch
results.value.unshift({
generatedAt: new Date().toLocaleTimeString(),
text: currentResultText.value,
})
isLoading.value = false
}
// Bring in Vercel for edge functions.
onMounted(inject)
</script>
Binary file added src/assets/images/icon-clock.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 14 additions & 2 deletions src/components/BaseButton.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,20 @@ const Template = (args) => ({
setup() {
return { args }
},
template: `<BaseButton>What a nice button!</BaseButton>`,
template: `
<BaseButton v-bind="args">
<span v-if="!args.isLoading">What a nice button!</span>
<span v-else>Nice loading text...</span>
</BaseButton>
`,
})

export const Primary = Template.bind({})
Primary.args = {}
Primary.args = {
isLoading: false,
}

export const LoadingState = Template.bind({})
LoadingState.args = {
isLoading: true,
}
17 changes: 16 additions & 1 deletion src/components/BaseButton.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
<template>
<button
class="p-2 text-white rounded-lg bg-primary-indigo hover:bg-cool-black"
class="p-2 text-white rounded-lg"
:disabled="isLoading"
:class="
isLoading
? 'bg-indigo-300 hover:bg-indigo-300'
: 'bg-primary-indigo hover:bg-cool-black'
"
>
<slot />
</button>
</template>

<script setup>
defineProps({
isLoading: {
type: Boolean,
default: false,
},
})
</script>
24 changes: 24 additions & 0 deletions src/components/ResultCard.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import ResultCard from './ResultCard.vue'

export default {
title: 'Result card (single)',
component: ResultCard,
}

const Template = (args) => ({
components: { ResultCard },
setup() {
return { args }
},
template: `<ResultCard v-bind="args" />`,
})

export const Primary = Template.bind({})
Primary.args = {
text: `
Lorem ipsum dolor sit amet consectetur adipisicing elit. Reiciendis nisi reprehenderit rem dicta totam ad temporibus recusandae quod. Dignissimos nesciunt quaerat aspernatur velit necessitatibus nam voluptatibus perferendis esse veritatis dicta.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Reiciendis nisi reprehenderit rem dicta totam ad temporibus recusandae quod. Dignissimos nesciunt quaerat aspernatur velit necessitatibus nam voluptatibus perferendis esse veritatis dicta.
`,
time: '9:48:13 AM',
order: 123,
}
42 changes: 42 additions & 0 deletions src/components/ResultCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<template>
<component
:is="tag"
class="flex flex-col-reverse border-4 border-neutral-200 rounded-2xl"
>
<h3
class="flex justify-end px-6 py-4 font-bold sm:justify-between bg-neutral-200"
>
<span class="hidden sm:inline-block">
<img
src="../assets/images/icon-clock.png"
role="presentation"
class="inline h-6 mr-3"
/>
Generated at {{ time }}
</span>
<span>Job Description #{{ order }}</span>
</h3>
<p class="px-6 pt-4 pb-6">{{ text }}</p>
</component>
</template>

<script setup>
defineProps({
tag: {
type: String,
default: 'li',
},
time: {
type: String,
required: true,
},
order: {
type: [String, Number],
required: true,
},
text: {
type: String,
required: true,
},
})
</script>
17 changes: 17 additions & 0 deletions src/components/ResultCardLoading.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ResultCardLoading from './ResultCardLoading.vue'

export default {
title: 'Result card loading skeleton',
component: ResultCardLoading,
}

const Template = (args) => ({
components: { ResultCardLoading },
setup() {
return { args }
},
template: `<ResultCardLoading v-bind="args" />`,
})

export const Primary = Template.bind({})
Primary.args = {}
45 changes: 45 additions & 0 deletions src/components/ResultCardLoading.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<div
role="alert"
aria-live="assertive"
class="flex flex-col-reverse w-full border-4 border-indigo-100 animate-pulse motion-safe:animate-none rounded-2xl"
>
<div class="flex justify-between px-6 py-4 font-bold bg-indigo-100">
<div class="hidden h-2 bg-indigo-200 rounded-lg w-52 sm:inline-block" />
<div class="h-2 bg-indigo-200 rounded-lg w-38 sm:w-1/5" />
</div>
<div class="block px-6 pt-4 pb-6">
<p class="mb-4 text-center text-indigo-400">
Generating a pop-out pitch!
</p>
<div class="grid grid-cols-3 grid-rows-2 gap-4">
<div class="h-2 col-span-2 row-span-1 bg-indigo-200 rounded"></div>
<div class="h-2 col-span-1 row-span-1 bg-indigo-200 rounded"></div>
<div class="h-2 col-span-1 row-span-2 bg-indigo-200 rounded"></div>
<div class="h-2 col-span-2 row-span-2 bg-indigo-200 rounded"></div>
</div>
</div>
</div>
</template>

<style scoped>
.loader {
width: 100%;
height: 200px;
background: linear-gradient(0.25turn, transparent, #fff, transparent),
linear-gradient(#ddd, #ddd),
radial-gradient(38px circle at 19px 19px, #ddd 50%, transparent 51%),
linear-gradient(#ddd, #ddd);
background-color: #fff;
background-repeat: no-repeat;
background-size: 100% 200px, 100%x 130px, 100px 100px, 225px 30px;
background-position: 0% 0, 0 0, 15px 140px, 65px 145px;
animation: loading 1.5s infinite;
}
@keyframes loading {
to {
background-position: 100% 0, 0 0, 15px 140px, 65px 145px;
}
}
</style>
4 changes: 2 additions & 2 deletions src/components/StepInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import BaseLabel from './BaseLabel.vue';
<template>
<div class="flex flex-col justify-between">
<BaseLabel :for="stepId" class="flex">
<span class="mr-9 md:mr-14 lg:mr-16">
<span class="mr-9 sm:mr-14 lg:mr-16">
<StepNumber :stepNumber="stepNumber" />
</span>
<span>
Expand All @@ -17,7 +17,7 @@ import BaseLabel from './BaseLabel.vue';
<BaseTextArea
:id="stepId"
:modelValue="stepText"
class="p-2"
class="h-40 p-2 sm:h-64 lg:h-72"
@input="$emit('update:stepText', $event.target.value)"
/>
</div>
Expand Down

1 comment on commit d2307f0

@vercel
Copy link

@vercel vercel bot commented on d2307f0 Apr 2, 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.