A web application for recommending Segway Ninebot scooters based on user preferences through an interactive quiz system.
Before getting started, make sure you have installed:
- Node.js
- npm, yarn, or pnpm as package manager
- Clone the repository (if you haven't):
git clone https://github.com/snoiclub/rekomendasi.git
cd rekomendasi- Install dependencies:
npm installor if using yarn:
yarn installor if using pnpm:
pnpm installRun the application in development mode:
npm run devThe application will run at http://localhost:3000
Open your browser and access the URL to view the application.
To create a production build:
npm run buildAfter the build completes, run:
npm startTo check code with ESLint:
npm run lintTo check TypeScript types:
npm run type-checkrekomendasi/
βββ public/
β βββ images/ # Scooter images
βββ src/
β βββ app/ # Next.js App Router
β β βββ layout.tsx # Main layout
β β βββ page.tsx # Home page
β β βββ globals.css # Global styles
β βββ components/
β β βββ Recommendation.tsx # Main quiz & recommendation component
β βββ data/
β β βββ quiz-questions.ts # Quiz questions data
β β βββ scooters.ts # Scooter list data
β β βββ type/ # JSON files for each scooter
β β βββ e2_e.json
β β βββ e2_e_ii.json
β β βββ ...
β βββ utils/
β βββ scoring.ts # Scoring & recommendation algorithm
βββ next.config.js # Next.js configuration
βββ tailwind.config.js # Tailwind CSS configuration
βββ tsconfig.json # TypeScript configuration
βββ package.json # Dependencies & scripts
To add a new scooter to the system:
Create a new file in src/data/type/ with the naming format: {model_name}.json
Example: src/data/type/e4_pro_e.json
Use the following format as a template:
{
"id": "e4_pro_e",
"name": "Ninebot E4 Pro E",
"brand": "Segway",
"price_min_juta": 12,
"price_max_juta": 15,
"battery_capacity_wh": 350,
"motor_power_nominal_w": 300,
"motor_power_max_w": 500,
"max_speed_kmh": 25,
"max_range_km": 40,
"max_payload_kg": 100,
"unit_weight_kg": 16,
"foldable": true,
"front_brake": "mechanical_disc",
"rear_brake": "drum",
"tire_size_inch": 10,
"tire_type": "tubeless",
"suspension": "front",
"ip_body": "IPX5",
"ip_battery": "IPX5",
"dimensions_open_mm": "1100 x 450 x 1200",
"dimensions_folded_mm": "1100 x 450 x 520",
"official_service": true,
"image_url": "/images/e4_pro_e.png",
"description": "Scooter description...",
"pros": [
"Advantage 1",
"Advantage 2"
],
"cons": [
"Disadvantage 1"
],
"warnings": []
}Add the scooter image to the public/images/ folder with a name matching the image_url in the JSON (example: e4_pro_e.png)
Open src/data/scooters.ts and:
- Import the newly created JSON file:
import e4_pro_e from './type/e4_pro_e.json';- Add it to the
SCOOTERSarray:
export const SCOOTERS: Scooter[] = [
// ... other scooters
e4_pro_e as Scooter,
];Required Fields:
id: Unique scooter ID (string)name: Scooter name (string)brand: Scooter brand (string)foldable: Whether it can be folded (boolean)front_brake: Front brake type (string)rear_brake: Rear brake type (string)tire_type: Tire type (string)suspension: Suspension type (string)official_service: Whether official service is available (boolean)description: Scooter description (string)pros: Array of advantages (string[])cons: Array of disadvantages (string[])
Optional Fields (can be null):
price_min_juta,price_max_jutabattery_capacity_whmotor_power_nominal_w,motor_power_max_wmax_speed_kmhmax_range_kmmax_payload_kgunit_weight_kgtire_size_inchip_body,ip_batterydimensions_open_mm,dimensions_folded_mmimage_urlwarnings: Array of warnings (string[])
To add or modify quiz questions, edit the src/data/quiz-questions.ts file.
{
id: 'question_id', // Unique question ID
question: 'Question?', // Question text
description: 'Description...', // Additional description (optional)
type: 'single' | 'multiple', // Type: single choice or multiple choice
required: true, // Whether it's required
options: [
{
id: 'option_id',
label: 'Option Label',
value: 'value', // Value to be stored
affects: [ // Array of affected fields
'max_range_km',
'battery_capacity_wh'
]
}
]
}{
id: 'budget_range',
question: 'What is your budget?',
description: 'Budget affects model recommendations',
type: 'single',
required: true,
options: [
{
id: 'budget_lt_10',
label: '<10 million',
value: 'lt_10',
affects: ['price_min_juta', 'price_max_juta']
},
{
id: 'budget_10_15',
label: '10-15 million',
value: '10_15',
affects: ['price_min_juta', 'price_max_juta']
}
]
}Add the new question to the QUIZ_QUESTIONS array in the same file.
The scoring algorithm calculates the match between quiz answers and scooter specifications. Scores are calculated based on several categories with specific weights:
- Battery (30%): Matches scooter range with daily distance needs
- Motor (30%): Matches motor power with rider weight and road terrain
- Features (20%): Matches features like speed, tires, brakes, suspension, IP rating
- Size (10%): Matches size/weight with user preferences
- Usage (10%): Matches usage type (commuter, hobby, long distance)
Total Score = (Battery Γ 0.30) + (Motor Γ 0.30) + (Features Γ 0.20) + (Size Γ 0.10) + (Usage Γ 0.10)
- Range Matching: If scooter range β₯ requirement, score 100. If less, proportional score.
- Motor Power Matching: If motor power β₯ requirement, score 100. If less, proportional score.
- Feature Matching: Each matching feature adds to score, mismatches generate warnings.
- Size Matching: Matching size = 100, not matching = 50.
- Usage Matching: Matching usage type = 100, not matching = 60.
To adjust the scoring algorithm, edit the src/utils/scoring.ts file:
- Change category weights in the
calculateScores()function (around line ~303-308) - Add new matching logic in the same function
- Adjust helper functions like
getRequiredRange(),getRequiredMotorPower(), etc.
Made with β€οΈ by SNOI DevHub