init: farmstar-survey-ui

This commit is contained in:
Mohamed Sohail 2024-01-19 12:51:33 +03:00
commit 2ff68432a2
Signed by: kamikazechaser
GPG Key ID: 7DD45520C01CD85D
23 changed files with 12656 additions and 0 deletions

24
.gitignore vendored Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View 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
View 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
View 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
View 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>

View 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
View 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

File diff suppressed because it is too large Load Diff

22
package.json Normal file
View 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
View File

@ -0,0 +1 @@
<template></template>

165
pages/distributor.vue Normal file
View 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
View 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 inputseeds, fertilizers, or crop protectionhas 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
View File

@ -0,0 +1 @@
<template></template>

24
pages/index.vue Normal file
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

22
tailwind.config.js Normal file
View 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
View File

@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

42
vueform.config.js Normal file
View 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'
}
}
}