mirror of
https://github.com/grassrootseconomics/farmstar-survey-ui.git
synced 2024-12-04 16:06:47 +01:00
init: farmstar-survey-ui
This commit is contained in:
commit
2ff68432a2
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
37
README.md
Normal file
37
README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# FarmStar Survey UI
|
||||
|
||||
## Required env variables
|
||||
|
||||
- `BASE_URL`
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install the dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
22
app.vue
Normal file
22
app.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<AppNavbar />
|
||||
<div class="bg-gray-100">
|
||||
<div class="max-w-screen-xl mx-auto">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: "FarmStar Survey",
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* You can keep or remove the body style depending on your preference */
|
||||
body {
|
||||
background-color: #f3f4f6; /* You can use the color code for light grey */
|
||||
margin: 0; /* Remove default body margin */
|
||||
}
|
||||
</style>
|
3
assets/css/input.css
Normal file
3
assets/css/input.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
72
components/App/AppNavbar.vue
Normal file
72
components/App/AppNavbar.vue
Normal file
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<nav class="border-gray-200 bg-gray-900">
|
||||
<div
|
||||
class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"
|
||||
>
|
||||
<NuxtLink to="/" class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||
<span
|
||||
class="self-center text-2xl font-semibold whitespace-nowrap text-white"
|
||||
>FarmStar Survey</span
|
||||
></NuxtLink
|
||||
>
|
||||
|
||||
<button
|
||||
@click="toggleMenu"
|
||||
data-collapse-toggle="navbar-default"
|
||||
type="button"
|
||||
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm rounded-lg md:hidden focus:outline-none focus:ring-2 text-gray-400 hover:bg-gray-700 focus:ring-gray-600"
|
||||
aria-controls="navbar-default"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 17 14"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M1 1h15M1 7h15M1 13h15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
:class="{ hidden: !isMenuOpen, 'lg:flex': isMenuOpen }"
|
||||
class="w-full md:block md:w-auto"
|
||||
id="navbar-default"
|
||||
>
|
||||
<ul
|
||||
class="font-medium flex flex-col p-4 md:p-0 mt-4 border rounded-lg md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 bg-gray-900 border-gray-700"
|
||||
>
|
||||
<li>
|
||||
<NuxtLink
|
||||
to="/about"
|
||||
class="block py-2 px-3 rounded md:border-0 md:p-0 text-white md:hover:text-blue-500 hover:bg-gray-700 hover:text-white md:hover:bg-transparent"
|
||||
>About</NuxtLink
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink
|
||||
to="/help"
|
||||
class="block py-2 px-3 rounded md:border-0 md:p-0 text-white md:hover:text-blue-500 hover:bg-gray-700 hover:text-white md:hover:bg-transparent"
|
||||
>Help</NuxtLink
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const isMenuOpen = ref(false);
|
||||
|
||||
const toggleMenu = () => {
|
||||
isMenuOpen.value = !isMenuOpen.value;
|
||||
};
|
||||
</script>
|
15
components/Card.vue
Normal file
15
components/Card.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-white mx-2 p-4 rounded border border-gray-300 mb-4 md:min-w-[250px] md:w-full md:mx-0 lg:mx-4"
|
||||
>
|
||||
<h2 class="text-l font-bold mb-2">{{ title }}</h2>
|
||||
<p class="text-sm text-gray-600 mb-4">{{ subtitle }}</p>
|
||||
<NuxtLink :to="link">
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded">Open</button>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps(["title", "subtitle", "link"]);
|
||||
</script>
|
5
components/FormSheet.vue
Normal file
5
components/FormSheet.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="bg-white rounded border border-gray-300 p-3">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
28
components/Notice.vue
Normal file
28
components/Notice.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div
|
||||
id="alert-additional-content-1"
|
||||
class="p-4 mb-4 text-blue-800 border border-blue-300 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400 dark:border-blue-800"
|
||||
role="alert"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<svg
|
||||
class="flex-shrink-0 w-4 h-4 me-2"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="sr-only">Info</span>
|
||||
<h3 class="text-lg font-medium">Info</h3>
|
||||
</div>
|
||||
<div class="mt-2 text-sm">{{ subtitle }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps(["subtitle"]);
|
||||
</script>
|
51
components/SuccessAlert.vue
Normal file
51
components/SuccessAlert.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div
|
||||
id="alert-additional-content-3"
|
||||
class="p-4 mb-4 text-green-800 border border-green-300 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400 dark:border-green-800"
|
||||
role="alert"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<svg
|
||||
class="flex-shrink-0 w-4 h-4 me-2"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="sr-only">Info</span>
|
||||
<h3 class="text-lg font-medium">{{ title }}</h3>
|
||||
</div>
|
||||
<div class="mt-2 mb-4 text-sm">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
<div class="flex">
|
||||
<NuxtLink :to="link">
|
||||
<button
|
||||
type="button"
|
||||
class="text-white bg-green-800 hover:bg-green-900 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-xs px-3 py-1.5 me-2 text-center inline-flex items-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800"
|
||||
>
|
||||
<svg
|
||||
class="me-2 h-3 w-3"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 14"
|
||||
>
|
||||
<path
|
||||
d="M10 0C4.612 0 0 5.336 0 7c0 1.742 3.546 7 10 7 6.454 0 10-5.258 10-7 0-1.664-4.612-7-10-7Zm0 10a3 3 0 1 1 0-6 3 3 0 0 1 0 6Z"
|
||||
/>
|
||||
</svg>
|
||||
{{ action }}
|
||||
</button>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps(["title", "subtitle", "link", "action"]);
|
||||
</script>
|
5
nuxt.config.ts
Normal file
5
nuxt.config.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
devtools: { enabled: false },
|
||||
modules: [ '@nuxtjs/tailwindcss', '@vueform/nuxt'],
|
||||
})
|
11459
package-lock.json
generated
Normal file
11459
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
package.json
Normal file
22
package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "farmstar-suvey-ui",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"dev:host": "nuxt dev --host",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/devtools": "latest",
|
||||
"@nuxtjs/tailwindcss": "^6.10.0",
|
||||
"@vueform/nuxt": "^1.4.0",
|
||||
"flowbite": "^2.2.0",
|
||||
"nuxt": "^3.8.2",
|
||||
"vue": "^3.3.8",
|
||||
"vue-router": "^4.2.5"
|
||||
}
|
||||
}
|
1
pages/about.vue
Normal file
1
pages/about.vue
Normal file
@ -0,0 +1 @@
|
||||
<template></template>
|
165
pages/distributor.vue
Normal file
165
pages/distributor.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="lg:flex items-start justify-center mt-4">
|
||||
<FormSheet class="mx-2 mb-4 lg:w-3/4">
|
||||
<ClientOnly>
|
||||
<Vueform
|
||||
v-if="!formSubmitted"
|
||||
ref="form$"
|
||||
validate-on="change"
|
||||
:form-data="(form$) => form$.requestData"
|
||||
@response="handleResponse"
|
||||
endpoint="distributor"
|
||||
>
|
||||
<div class="text-xl mb-1 col-span-12">
|
||||
<div class="border-b border-gray-300 pb-2">
|
||||
Distributor onboarding survey
|
||||
</div>
|
||||
</div>
|
||||
<TextElement
|
||||
name="phone"
|
||||
label="What is your primary phone number?"
|
||||
description="Enter a valid Kenyan phone number starting with 07 or 01 that you registered with."
|
||||
rules="required|phone"
|
||||
/>
|
||||
<SelectElement
|
||||
name="distributor_name"
|
||||
label="What is the name of the agri-input distributor you work with?"
|
||||
:native="false"
|
||||
:items="{
|
||||
maraba_investments: 'Maraba Investments',
|
||||
farmers_center: 'Farmers Center',
|
||||
farmers_world: 'Farmers World',
|
||||
farmers_desk: 'Farmers Desk',
|
||||
mazao_na_afya: 'Mazao na Afya',
|
||||
}"
|
||||
rules="required"
|
||||
/>
|
||||
<SelectElement
|
||||
name="type"
|
||||
label="What types of fertilizers do you current stock at your distributor?"
|
||||
:native="false"
|
||||
:items="{
|
||||
synthetic: 'Synthetic/Chemical',
|
||||
organic: 'Organic',
|
||||
both: 'Both',
|
||||
}"
|
||||
rules="required"
|
||||
/>
|
||||
<TextElement
|
||||
name="synthetic_percentage"
|
||||
label="In the past six months, what percentage of your fertilizer sales comprised synthetic fertilizers?"
|
||||
description="Enter a percentage value"
|
||||
rules="required|min:0|numeric|max:100"
|
||||
input-type="number"
|
||||
:conditions="[['type', 'both']]"
|
||||
/>
|
||||
<TextElement
|
||||
name="organic_percentage"
|
||||
label="In the past six months, what percentage of your fertilizer sales comprised organic fertilizers?"
|
||||
description="Enter a percentage value"
|
||||
rules="required|min:0|numeric|max:100"
|
||||
input-type="number"
|
||||
:conditions="[['type', 'both']]"
|
||||
/>
|
||||
<TagsElement
|
||||
name="synthetic_factors"
|
||||
label="What factors do you believe influence farmers' preferences for synthetic fertilizers? (Select all that apply)"
|
||||
:items="{
|
||||
increased_crop_yield: 'Increased Crop Yield',
|
||||
cost_effective: 'Cost-effectiveness',
|
||||
ease_of_application: 'Ease of application',
|
||||
availability: 'Availability',
|
||||
marketing: 'Marketing/Advertising',
|
||||
govt_incentives: 'Government Incentives',
|
||||
other: 'Other',
|
||||
}"
|
||||
rules="required"
|
||||
:conditions="[['type', ['both', 'synthetic']]]"
|
||||
/>
|
||||
<TagsElement
|
||||
name="organic_factors"
|
||||
label="What factors do you believe influence farmers' preferences for organic fertilizers? (Select all that apply)"
|
||||
:items="{
|
||||
increased_crop_yield: 'Increased Crop Yield',
|
||||
cost_effective: 'Cost-effectiveness',
|
||||
ease_of_application: 'Ease of application',
|
||||
availability: 'Availability',
|
||||
marketing: 'Marketing/Advertising',
|
||||
govt_incentives: 'Government Incentives',
|
||||
other: 'Other',
|
||||
environmental_concerns: 'Environmental concerns',
|
||||
soil_health: 'Soil health improvement',
|
||||
consumer_demand: 'Consumer demand for organic produce',
|
||||
}"
|
||||
rules="required"
|
||||
:conditions="[['type', ['both', 'organic']]]"
|
||||
/>
|
||||
<TextareaElement
|
||||
name="trends"
|
||||
rules="required|max:250"
|
||||
label="Have you noticed any specific trends or patterns in farmers' choices between synthetic and organic fertilizers?"
|
||||
/>
|
||||
<TextareaElement
|
||||
name="obstacles"
|
||||
rules="required|max:250"
|
||||
:conditions="[['type', ['both', 'organic']]]"
|
||||
label="Are there any obstacles faced in promoting or selling organic fertilizers compared to synthetic fertilizers? If yes, please specify."
|
||||
/>
|
||||
<SelectElement
|
||||
name="need_education"
|
||||
label="Do farmers express a need for more education or information about the benefits of organic fertilizers compared to synthetic ones?"
|
||||
:native="false"
|
||||
:items="{
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
}"
|
||||
rules="required"
|
||||
:conditions="[['type', ['both', 'organic']]]"
|
||||
/>
|
||||
<TextareaElement
|
||||
name="organic_strategies"
|
||||
rules="required|max:250"
|
||||
:conditions="[['type', ['both', 'organic']]]"
|
||||
label="What strategies have been most effective in encouraging the adoption of organic fertilizers among walk-in farmers?"
|
||||
/>
|
||||
<TextareaElement
|
||||
name="farmstar_strategies"
|
||||
rules="required|max:500"
|
||||
:conditions="[['type', ['both', 'organic']]]"
|
||||
label="In your opinion, what strategies could Farm Star use to encourage an increased adoption of organic fertilizers among farmers?"
|
||||
/>
|
||||
<ButtonElement name="submit" add-class="mt-2" submits>
|
||||
Submit
|
||||
</ButtonElement>
|
||||
</Vueform>
|
||||
<SuccessAlert
|
||||
v-else
|
||||
title="Submission Successful"
|
||||
subtitle="Thank you for submitting the form."
|
||||
link="/"
|
||||
action="Home"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</FormSheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const form$ = ref(null);
|
||||
const formSubmitted = ref(false);
|
||||
|
||||
const handleResponse = async (response, form$) => {
|
||||
if (response.status <= 201) {
|
||||
formSubmitted.value = true;
|
||||
} else {
|
||||
form$.messageBag.append(response.data?.message);
|
||||
if (Object.keys(response.data.data)[0]?.message) {
|
||||
form$.messageBag.append(
|
||||
Object.keys(response.data.data)[0] +
|
||||
": " +
|
||||
Object.values(response.data.data)[0]?.message
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
434
pages/farmer.vue
Normal file
434
pages/farmer.vue
Normal file
@ -0,0 +1,434 @@
|
||||
<template>
|
||||
<div class="lg:flex items-start justify-center mt-4">
|
||||
<FormSheet class="mx-2 mb-4 lg:w-3/4">
|
||||
<ClientOnly>
|
||||
<Vueform
|
||||
v-if="!formSubmitted"
|
||||
ref="form$"
|
||||
validate-on="change"
|
||||
:form-data="(form$) => form$.requestData"
|
||||
@response="handleResponse"
|
||||
endpoint="farmer"
|
||||
>
|
||||
<div class="text-xl mb-1 col-span-12">
|
||||
<div class="border-b border-gray-300 pb-2">
|
||||
Farmer onboarding survey
|
||||
</div>
|
||||
</div>
|
||||
<TextElement
|
||||
name="phone"
|
||||
label="What is your primary phone number?"
|
||||
description="Enter a valid Kenyan phone number starting with 07 or 01 that you registered with."
|
||||
rules="required|phone"
|
||||
/>
|
||||
<div class="text mb-1 mt-1 col-span-12">
|
||||
<div class="border-b border-gray-300 pb-2">Farm Details</div>
|
||||
</div>
|
||||
<GroupElement name="farm_details">
|
||||
<SelectElement
|
||||
name="county"
|
||||
label="What county are you located in?"
|
||||
:native="false"
|
||||
:items="{
|
||||
kirinyaga: 'Kirinyaga',
|
||||
muranga: 'Muranga',
|
||||
nakuru: 'Nakuru',
|
||||
meru: 'Meru',
|
||||
uasin_gishu: 'Uasin Gishu',
|
||||
kajiado: 'Kajiado',
|
||||
}"
|
||||
rules="required"
|
||||
/>
|
||||
<TextElement
|
||||
name="sub_county"
|
||||
label="What sub-county are you located in?"
|
||||
rules="required|max:50"
|
||||
/>
|
||||
<TextElement
|
||||
name="farming_area_acres"
|
||||
input-type="number"
|
||||
label="How many acres of land are you currently farming?"
|
||||
rules="required|min:1|numeric"
|
||||
/>
|
||||
<TagsElement
|
||||
name="planned_crops"
|
||||
label="Which crops do you plan to grow in the coming season?"
|
||||
:items="{
|
||||
rice: 'Rice',
|
||||
coffee: 'Coffee',
|
||||
tea: 'Tea',
|
||||
sugarcane: 'Sugarcane',
|
||||
miraa: 'Miraa',
|
||||
avocados: 'Avocados',
|
||||
maize: 'Maize',
|
||||
potatoes: 'Potatoes',
|
||||
sorghum: 'Sorghum',
|
||||
other_fruits: 'Other fruits',
|
||||
other_vegetables: 'Other vegetables',
|
||||
other_grains: 'Other grains',
|
||||
}"
|
||||
rules="required"
|
||||
description="Select all options that apply."
|
||||
/>
|
||||
</GroupElement>
|
||||
<div class="text mt-4 col-span-12">
|
||||
<div class="border-b border-gray-300 pb-2">
|
||||
Past Harvest Details
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12">
|
||||
<Notice
|
||||
subtitle="Select all the crops that you have planted in the last 3 years, the average harvest you have gotten (in kg/acre) and how much you have earned (in ksh/kg) on selling each crop."
|
||||
/>
|
||||
</div>
|
||||
<ListElement
|
||||
name="past_harvest_details"
|
||||
label="Harvest details for the last 3 years"
|
||||
:min="1"
|
||||
>
|
||||
<template #default="{ index }">
|
||||
<p>Crop {{ index + 1 }}</p>
|
||||
<ObjectElement :name="index">
|
||||
<SelectElement
|
||||
name="crop_type"
|
||||
rules="required"
|
||||
:search="true"
|
||||
:items="{
|
||||
rice: 'Rice',
|
||||
coffee: 'Coffee',
|
||||
tea: 'Tea',
|
||||
sugarcane: 'Sugarcane',
|
||||
miraa: 'Miraa',
|
||||
avocados: 'Avocados',
|
||||
maize: 'Maize',
|
||||
potatoes: 'Potatoes',
|
||||
sorghum: 'Sorghum',
|
||||
other_fruits: 'Other fruits',
|
||||
other_vegetables: 'Other vegetables',
|
||||
other_grains: 'Other grains',
|
||||
}"
|
||||
/>
|
||||
<TextElement
|
||||
name="average_harvest"
|
||||
label="Average harvest"
|
||||
description="Average yield/output in kg/acre."
|
||||
rules="required|min:1|numeric"
|
||||
/>
|
||||
<TextElement
|
||||
name="average_earning"
|
||||
label="Average earning"
|
||||
description="Average yield/output in ksh/kg."
|
||||
rules="required|min:1|numeric"
|
||||
/>
|
||||
<div class="col-span-12">
|
||||
<div class="border-t border-gray-300 mt-1"></div>
|
||||
</div>
|
||||
</ObjectElement>
|
||||
</template>
|
||||
</ListElement>
|
||||
<div class="text mt-4 col-span-12">
|
||||
<div class="border-b border-gray-300 pb-2">
|
||||
Budget and Expenditure Details
|
||||
</div>
|
||||
</div>
|
||||
<TextElement
|
||||
name="total_expenditure"
|
||||
input-type="number"
|
||||
label="On average, how much do you spend on seeds, fertilizers, and crop protection (herbicides, pesticides, etc.) for a year?"
|
||||
description="Average in ksh. per year"
|
||||
rules="required|min:1|numeric"
|
||||
/>
|
||||
<GroupElement name="expenditure_details">
|
||||
<StaticElement
|
||||
name="static"
|
||||
description="Total percentage should add upto 100%"
|
||||
>How would you say your budget is split between seeds,
|
||||
fertilizers, and crop protection?</StaticElement
|
||||
>
|
||||
<TextElement
|
||||
columns="4"
|
||||
name="seeds_expenditure_percentage"
|
||||
input-type="number"
|
||||
label="Seeds %"
|
||||
rules="required|min:0|numeric|max:100"
|
||||
/>
|
||||
<TextElement
|
||||
columns="4"
|
||||
name="fertilizer_expenditure_percentage"
|
||||
input-type="number"
|
||||
label="Fertilizer %"
|
||||
rules="required|min:0|numeric|max:100"
|
||||
F
|
||||
/>
|
||||
<TextElement
|
||||
columns="4"
|
||||
name="crops_protection_expenditure_percentage"
|
||||
input-type="number"
|
||||
label="Crops Protection %"
|
||||
rules="required|min:0|numeric|max:100"
|
||||
/>
|
||||
</GroupElement>
|
||||
<GroupElement name="fertilizer_expenditure_details">
|
||||
<StaticElement
|
||||
name="static"
|
||||
description="Total percentage should add upto 100%"
|
||||
>How is your spend on fertilizers split between
|
||||
synthetic/inorganic fertilizers (like DAP, NPK, etc.) and
|
||||
organic/natural fertilizers (like manure, compost,
|
||||
etc.)</StaticElement
|
||||
>
|
||||
<TextElement
|
||||
columns="6"
|
||||
name="synthetic_fertilizers_expenditure_percentage"
|
||||
input-type="number"
|
||||
label="Synthetic Fertilizers %"
|
||||
rules="required|min:0|numeric|max:100"
|
||||
/>
|
||||
<TextElement
|
||||
columns="6"
|
||||
name="natural_fertilizers_expenditure_percentage"
|
||||
input-type="number"
|
||||
label="Natural Fertilizers %"
|
||||
rules="required|min:0|numeric|max:100"
|
||||
/>
|
||||
</GroupElement>
|
||||
|
||||
<StaticElement
|
||||
name="static"
|
||||
description="You can add upto 3 expenses."
|
||||
>Which input—seeds, fertilizers, or crop protection—has increased
|
||||
the most in cost for you over the past 3 years?</StaticElement
|
||||
>
|
||||
<ListElement name="increased_expenses" :min="0">
|
||||
<template #default="{ index }">
|
||||
<p>Expense</p>
|
||||
<ObjectElement :name="index">
|
||||
<SelectElement
|
||||
rules="required"
|
||||
name="expense_type"
|
||||
:native="false"
|
||||
:items="{
|
||||
fertilizers: 'Fertilizers',
|
||||
seeds: 'Seeds',
|
||||
crop_protection: 'Crop Protection',
|
||||
}"
|
||||
/>
|
||||
<TagsElement
|
||||
rules="required"
|
||||
name="increased_expense_actions"
|
||||
label="How have you dealt with the cost increase?"
|
||||
:items="{
|
||||
no_change: 'No Change',
|
||||
cheap_alternative: 'Purchased and used cheaper alternative',
|
||||
less_quantity: 'Purchased and used less quantity',
|
||||
more_quantity: 'Purchased more',
|
||||
other: 'Other',
|
||||
}"
|
||||
description="Select all options that apply."
|
||||
/>
|
||||
<SelectElement
|
||||
name="expense_action_overall_effect"
|
||||
label="Have these changes affected how much you harvest?"
|
||||
:native="false"
|
||||
rules="required"
|
||||
:items="{
|
||||
increased_yields: 'Increased yields',
|
||||
no_change_yields: 'No change to yields',
|
||||
decreased_yields: 'Decreased yields',
|
||||
other: 'Other',
|
||||
}"
|
||||
/>
|
||||
<div class="col-span-12">
|
||||
<div class="border-t border-gray-300 mt-1"></div>
|
||||
</div>
|
||||
</ObjectElement>
|
||||
</template>
|
||||
</ListElement>
|
||||
<div class="text mt-4 col-span-12">
|
||||
<div class="border-b border-gray-300 pb-2">EverGrow</div>
|
||||
</div>
|
||||
<SelectElement
|
||||
name="evergrow_past"
|
||||
:native="false"
|
||||
label="Have you used EverGrow before?"
|
||||
rules="required"
|
||||
:items="{
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
}"
|
||||
/>
|
||||
<TextElement
|
||||
name="evergrow_first"
|
||||
label="What year did you first begin using EverGrow?"
|
||||
:conditions="[['evergrow_past', 'yes']]"
|
||||
input-type="number"
|
||||
rules="required|min:2018|numeric"
|
||||
/>
|
||||
<TextElement
|
||||
name="evergrow_application"
|
||||
label="What is your typical application rate of EverGrow?"
|
||||
description="Average in kg/acre."
|
||||
:conditions="[['evergrow_past', 'yes']]"
|
||||
input-type="number"
|
||||
rules="required|min:1|numeric"
|
||||
/>
|
||||
<TagsElement
|
||||
name="evergrow_crops"
|
||||
label="What crops have you seen the most success with using EverGrow?"
|
||||
:items="{
|
||||
rice: 'Rice',
|
||||
coffee: 'Coffee',
|
||||
tea: 'Tea',
|
||||
sugarcane: 'Sugarcane',
|
||||
miraa: 'Miraa',
|
||||
avocados: 'Avocados',
|
||||
maize: 'Maize',
|
||||
potatoes: 'Potatoes',
|
||||
sorghum: 'Sorghum',
|
||||
other_fruits: 'Other fruits',
|
||||
other_vegetables: 'Other vegetables',
|
||||
other_grains: 'Other grains',
|
||||
}"
|
||||
:conditions="[['evergrow_past', 'yes']]"
|
||||
description="Select all options that apply."
|
||||
rules="required"
|
||||
/>
|
||||
<TextElement
|
||||
columns="12"
|
||||
name="evergrow_yield"
|
||||
input-type="number"
|
||||
label="How much have your yields typically improved by when using EverGrow (vs. when not)?"
|
||||
description="Enter a percentage value"
|
||||
:conditions="[['evergrow_past', 'yes']]"
|
||||
rules="required|min:0|max:100|numeric"
|
||||
/>
|
||||
<div class="text mt-4 col-span-12">
|
||||
<div class="border-b border-gray-300 pb-2">Other Fertilizers</div>
|
||||
</div>
|
||||
<SelectElement
|
||||
label="Do you use any other types of fertilizer on your land?"
|
||||
name="other_fertilizers"
|
||||
:native="false"
|
||||
:items="{
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
not_sure: 'Not Sure',
|
||||
}"
|
||||
rules="required"
|
||||
/>
|
||||
<StaticElement
|
||||
:conditions="[['other_fertilizers', 'yes']]"
|
||||
name="static"
|
||||
description="You can add upto 3 other fertilizer types."
|
||||
>Select all other fertilizer types that you have used in the
|
||||
past?</StaticElement
|
||||
>
|
||||
<ListElement
|
||||
name="other_fertilizers_details"
|
||||
:conditions="[['other_fertilizers', 'yes']]"
|
||||
rules="required"
|
||||
>
|
||||
<template #default="{ index }">
|
||||
<p>Fertilizer</p>
|
||||
<ObjectElement :name="index">
|
||||
<SelectElement
|
||||
rules="required"
|
||||
name="other_fertilizer_type"
|
||||
:native="false"
|
||||
:items="{
|
||||
synthetic: 'Synthetic Fertilizer',
|
||||
commercial_organic: 'Commercial Organic Fertilizer',
|
||||
self_made: 'Self-made fertilizer/manure',
|
||||
}"
|
||||
/>
|
||||
<TextElement
|
||||
name="other_fertilizer_application"
|
||||
input-type="number"
|
||||
label="What is your typical application rate of this type of fertilizer?"
|
||||
description="Average in kg/acre."
|
||||
rules="required|min:1|numeric"
|
||||
/>
|
||||
<TagsElement
|
||||
rules="required"
|
||||
name="other_fertilizer_crops"
|
||||
label="What crops have you seen the most success with using this type of fertilizer"
|
||||
:items="{
|
||||
rice: 'Rice',
|
||||
coffee: 'Coffee',
|
||||
tea: 'Tea',
|
||||
sugarcane: 'Sugarcane',
|
||||
miraa: 'Miraa',
|
||||
avocados: 'Avocados',
|
||||
maize: 'Maize',
|
||||
potatoes: 'Potatoes',
|
||||
sorghum: 'Sorghum',
|
||||
other_fruits: 'Other fruits',
|
||||
other_vegetables: 'Other vegetables',
|
||||
other_grains: 'Other grains',
|
||||
}"
|
||||
description="Select all options that apply."
|
||||
/>
|
||||
<div class="col-span-12">
|
||||
<div class="border-t border-gray-300 mt-1"></div>
|
||||
</div>
|
||||
</ObjectElement>
|
||||
</template>
|
||||
</ListElement>
|
||||
<div class="text mt-4 col-span-12">
|
||||
<div class="border-b border-gray-300 pb-2">
|
||||
Fertilizer Purchase Details
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12">
|
||||
<Notice
|
||||
subtitle="This applies for all types of fertilizers you purchase including Evergrow. "
|
||||
/>
|
||||
</div>
|
||||
<TagsElement
|
||||
name="purchase_channels"
|
||||
rules="required"
|
||||
label="Where do you typically purchase your fertilizer from?"
|
||||
:items="{
|
||||
manufacturers: 'Direct from manufacturers',
|
||||
distributors: 'Distributors',
|
||||
resellers: 'Resellers',
|
||||
farmers: 'Fellow farmers',
|
||||
other: 'Other',
|
||||
}"
|
||||
description="Select all options that apply."
|
||||
/>
|
||||
<ButtonElement name="submit" add-class="mt-2" submits>
|
||||
Submit
|
||||
</ButtonElement>
|
||||
</Vueform>
|
||||
<SuccessAlert
|
||||
v-else
|
||||
title="Submission Successful"
|
||||
subtitle="Thank you for submitting the form."
|
||||
link="/"
|
||||
action="Home"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</FormSheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const form$ = ref(null);
|
||||
const formSubmitted = ref(false);
|
||||
|
||||
const handleResponse = async (response, form$) => {
|
||||
if (response.status <= 201) {
|
||||
formSubmitted.value = true;
|
||||
} else {
|
||||
form$.messageBag.append(response.data?.error);
|
||||
if (Object.keys(response.data.data)[0]?.message) {
|
||||
form$.messageBag.append(
|
||||
Object.keys(response.data.data)[0] +
|
||||
": " +
|
||||
Object.values(response.data.data)[0]?.message
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
1
pages/help.vue
Normal file
1
pages/help.vue
Normal file
@ -0,0 +1 @@
|
||||
<template></template>
|
24
pages/index.vue
Normal file
24
pages/index.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="mt-4 flex flex-col md:flex-row md:space-x-4">
|
||||
<Card
|
||||
title="Sign up"
|
||||
subtitle="To join the FarmStar rewards program."
|
||||
link="/registration"
|
||||
/>
|
||||
<Card
|
||||
title="Farmer onboarding survey"
|
||||
subtitle="Tell us about what and how you farm!"
|
||||
link="/farmer"
|
||||
/>
|
||||
<Card
|
||||
title="Distributor onboarding survey"
|
||||
subtitle="Tell us about the agri-inputs you sell!"
|
||||
link="/distributor"
|
||||
/>
|
||||
<Card
|
||||
title="Record a purchase"
|
||||
subtitle="Of EverGrow organic fertilizer."
|
||||
link="/transaction"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
94
pages/registration.vue
Normal file
94
pages/registration.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="lg:flex items-start justify-center mt-4">
|
||||
<FormSheet class="mx-2 lg:w-3/4">
|
||||
<ClientOnly>
|
||||
<Vueform
|
||||
v-if="!formSubmitted"
|
||||
ref="form$"
|
||||
validate-on="change"
|
||||
:form-data="(form$) => form$.requestData"
|
||||
@response="handleResponse"
|
||||
endpoint="registration"
|
||||
>
|
||||
<div class="text-xl mb-4 col-span-12">
|
||||
<div class="border-b border-gray-300 pb-2">Sign up</div>
|
||||
</div>
|
||||
<GroupElement name="personal_information">
|
||||
<TextElement
|
||||
name="name"
|
||||
label="What is your full name?"
|
||||
rules="required|max:150"
|
||||
/>
|
||||
<SelectElement
|
||||
name="age_group"
|
||||
:native="false"
|
||||
label="How old are you?"
|
||||
description="Select the age group you fall into."
|
||||
:items="['20-29', '30-39', '40-49', '50-59', '60-69', '70+']"
|
||||
rules="required"
|
||||
/>
|
||||
<SelectElement
|
||||
name="gender"
|
||||
label="What is your gender?"
|
||||
:native="false"
|
||||
:items="{
|
||||
female: 'Female',
|
||||
male: 'Male',
|
||||
transgender: 'Transgender',
|
||||
other: 'Other',
|
||||
no_response: 'Prefer not to response',
|
||||
}"
|
||||
rules="required"
|
||||
/>
|
||||
<TextElement
|
||||
name="phone"
|
||||
label="What is your primary phone number?"
|
||||
description="Enter a valid Kenyan phone number starting with 07 or 01."
|
||||
rules="required|phone"
|
||||
/>
|
||||
<SelectElement
|
||||
name="participant_type"
|
||||
label="Are you a farmer or a distributor?"
|
||||
:native="false"
|
||||
:items="{
|
||||
farmer: 'Farmer',
|
||||
distributor: 'Distributor',
|
||||
}"
|
||||
rules="required"
|
||||
/>
|
||||
</GroupElement>
|
||||
<ButtonElement name="submit" add-class="mt-2" submits>
|
||||
Register
|
||||
</ButtonElement>
|
||||
</Vueform>
|
||||
<SuccessAlert
|
||||
v-else
|
||||
title="Submission Successful"
|
||||
subtitle="Registration details submitted successfully. Your survey account is pending approval."
|
||||
link="/"
|
||||
action="Home"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</FormSheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const form$ = ref(null);
|
||||
const formSubmitted = ref(false);
|
||||
|
||||
const handleResponse = async (response, form$) => {
|
||||
if (response.status <= 201) {
|
||||
formSubmitted.value = true;
|
||||
} else {
|
||||
form$.messageBag.append(response.data?.message);
|
||||
if (Object.keys(response.data.data)[0]?.message) {
|
||||
form$.messageBag.append(
|
||||
Object.keys(response.data.data)[0] +
|
||||
": " +
|
||||
Object.values(response.data.data)[0]?.message
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
126
pages/transaction.vue
Normal file
126
pages/transaction.vue
Normal file
@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div class="lg:flex items-start justify-center mt-4">
|
||||
<FormSheet class="mx-2 lg:w-3/4">
|
||||
<ClientOnly>
|
||||
<Vueform
|
||||
v-if="!formSubmitted"
|
||||
ref="form$"
|
||||
validate-on="change"
|
||||
:form-data="(form$) => form$.requestData"
|
||||
@response="handleResponse"
|
||||
endpoint="transaction"
|
||||
>
|
||||
<div class="text-xl mb-4 col-span-12">
|
||||
<div class="border-b border-gray-300 pb-2">Record a purchase</div>
|
||||
</div>
|
||||
<TextElement
|
||||
name="phone"
|
||||
label="What is your phone number?"
|
||||
rules="required"
|
||||
/>
|
||||
<SelectElement
|
||||
name="tx"
|
||||
:native="false"
|
||||
label="Did you buy or sell EverGrow Organic Fertilizer?"
|
||||
:items="{
|
||||
buy: 'Buy',
|
||||
sell: 'Sell',
|
||||
}"
|
||||
/>
|
||||
<SelectElement
|
||||
name="distributor_name"
|
||||
label="Select the name of the distributor you purchased EverGrow from."
|
||||
:native="false"
|
||||
:items="{
|
||||
maraba_investments: 'Maraba Investments',
|
||||
farmers_center: 'Farmers Center',
|
||||
farmers_world: 'Farmers World',
|
||||
farmers_desk: 'Farmers Desk',
|
||||
mazao_na_afya: 'Mazao na Afya',
|
||||
}"
|
||||
rules="required"
|
||||
:conditions="[['tx', 'buy']]"
|
||||
/>
|
||||
<TextElement
|
||||
name="buy"
|
||||
label="Enter the phone number of the person you bought EverGrow from."
|
||||
rules="required"
|
||||
:conditions="[['tx', 'buy']]"
|
||||
/>
|
||||
<TextElement
|
||||
name="sell"
|
||||
label="Enter the phone number of the customer you sold EverGrow to."
|
||||
rules="required"
|
||||
:conditions="[['tx', 'sell']]"
|
||||
/>
|
||||
<DateElement
|
||||
:min="new Date(2024, 0, 23)"
|
||||
name="buy_date"
|
||||
label="When did you make this purchase?"
|
||||
rules="required"
|
||||
:conditions="[['tx', 'buy']]"
|
||||
/>
|
||||
<DateElement
|
||||
:min="new Date(2024, 0, 23)"
|
||||
name="sell_date"
|
||||
label="When did you make this sale?"
|
||||
rules="required"
|
||||
:conditions="[['tx', 'sell']]"
|
||||
/>
|
||||
<TextElement
|
||||
name="evergrow_quantity"
|
||||
label="How much EverGrow did you purchase?"
|
||||
description="1x50kg bag of EverGrow = 1, 2 bags = 2, etc."
|
||||
rules="required|numeric|min:1|max:20"
|
||||
input-type="number"
|
||||
:conditions="[['tx', 'buy']]"
|
||||
/>
|
||||
<TextElement
|
||||
name="evergrow_quantity"
|
||||
label="How much EverGrow did you sell?"
|
||||
description="1x50kg bag of EverGrow = 1, 2 bags = 2, etc."
|
||||
rules="required|numeric|min:1|max:20"
|
||||
input-type="number"
|
||||
:conditions="[['tx', 'sell']]"
|
||||
/>
|
||||
<TextareaElement
|
||||
name="feedback"
|
||||
:conditions="[['tx', ['sell', 'buy']]]"
|
||||
rules="max:300"
|
||||
label="Do you have any comments or feedback for FarmStar?"
|
||||
/>
|
||||
<ButtonElement name="submit" add-class="mt-2" submits>
|
||||
Submit
|
||||
</ButtonElement>
|
||||
</Vueform>
|
||||
<SuccessAlert
|
||||
v-else
|
||||
title="Submission Successful"
|
||||
subtitle="Transaction details submitted successfully."
|
||||
link="/"
|
||||
action="Home"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</FormSheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const form$ = ref(null);
|
||||
const formSubmitted = ref(false);
|
||||
|
||||
const handleResponse = async (response, form$) => {
|
||||
if (response.status <= 201) {
|
||||
formSubmitted.value = true;
|
||||
} else {
|
||||
form$.messageBag.append(response.data?.message);
|
||||
if (Object.keys(response.data.data)[0]?.message) {
|
||||
form$.messageBag.append(
|
||||
Object.keys(response.data.data)[0] +
|
||||
": " +
|
||||
Object.values(response.data.data)[0]?.message
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
22
tailwind.config.js
Normal file
22
tailwind.config.js
Normal file
@ -0,0 +1,22 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./components/**/*.{js,vue,ts}",
|
||||
"./layouts/**/*.vue",
|
||||
"./pages/**/*.vue",
|
||||
"./plugins/**/*.{js,ts}",
|
||||
"./nuxt.config.{js,ts}",
|
||||
'./vueform.config.{js,ts}',
|
||||
'./node_modules/@vueform/vueform/themes/tailwind/**/*.vue',
|
||||
'./node_modules/@vueform/vueform/themes/tailwind/**/*.js',
|
||||
'./node_modules/flowbite/**/*.{js,ts}'
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
require('flowbite/plugin'),
|
||||
require('@vueform/vueform/tailwind')
|
||||
],
|
||||
}
|
||||
|
4
tsconfig.json
Normal file
4
tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
42
vueform.config.js
Normal file
42
vueform.config.js
Normal file
@ -0,0 +1,42 @@
|
||||
import en from '@vueform/vueform/locales/en'
|
||||
import tailwind from '@vueform/vueform/themes/tailwind'
|
||||
|
||||
import { Validator } from '@vueform/vueform'
|
||||
|
||||
const phoneRule = class extends Validator {
|
||||
get message() {
|
||||
return 'The What is your primary phone number? filed requires a valid phone number'
|
||||
}
|
||||
|
||||
check(value) {
|
||||
return /^(07|01)(\d){8}$/.test(value);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
theme: tailwind,
|
||||
locales: { en },
|
||||
locale: 'en',
|
||||
floatPlaceholders: false,
|
||||
rules: {
|
||||
phone: phoneRule,
|
||||
},
|
||||
endpoints: {
|
||||
registration: {
|
||||
url: `${process.env.BASE_URL}/registration`,
|
||||
method: 'post'
|
||||
},
|
||||
transaction: {
|
||||
url: `${process.env.BASE_URL}/transaction`,
|
||||
method: 'post'
|
||||
},
|
||||
farmer: {
|
||||
url: `${process.env.BASE_URL}/farmer`,
|
||||
method: 'post'
|
||||
},
|
||||
distributor: {
|
||||
url: `${process.env.BASE_URL}/distributor`,
|
||||
method: 'post'
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user