Compare commits

...

1 Commits

Author SHA1 Message Date
Flatlogic Bot
3cdc49d576 Jk 2026-03-29 09:24:23 +00:00
7 changed files with 1581 additions and 146 deletions

View File

@ -16,6 +16,7 @@ const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search'); const searchRoutes = require('./routes/search');
const sqlRoutes = require('./routes/sql'); const sqlRoutes = require('./routes/sql');
const pexelsRoutes = require('./routes/pexels'); const pexelsRoutes = require('./routes/pexels');
const publicFormsRoutes = require('./routes/public_forms');
const openaiRoutes = require('./routes/openai'); const openaiRoutes = require('./routes/openai');
@ -100,6 +101,7 @@ app.use(bodyParser.json());
app.use('/api/auth', authRoutes); app.use('/api/auth', authRoutes);
app.use('/api/file', fileRoutes); app.use('/api/file', fileRoutes);
app.use('/api/pexels', pexelsRoutes); app.use('/api/pexels', pexelsRoutes);
app.use('/api/public/forms', publicFormsRoutes);
app.enable('trust proxy'); app.enable('trust proxy');

View File

@ -0,0 +1,203 @@
const express = require('express');
const db = require('../db/models');
const Loan_applicationsDBApi = require('../db/api/loan_applications');
const Contact_messagesDBApi = require('../db/api/contact_messages');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const CONTACT_SUBJECTS = new Set([
'loan_application_help',
'loan_terms',
'repayment',
'collateral',
'partnership',
'other',
]);
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const phonePattern = /^[+()\d\s-]{7,20}$/;
function createBadRequest(message) {
const error = new Error(message);
error.code = 400;
return error;
}
function cleanText(value) {
return typeof value === 'string' ? value.trim() : '';
}
function requireText(value, label, minLength = 2) {
const cleaned = cleanText(value);
if (cleaned.length < minLength) {
throw createBadRequest(`${label} is required.`);
}
return cleaned;
}
function requirePositiveNumber(value, label) {
const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed <= 0) {
throw createBadRequest(`${label} must be a valid amount.`);
}
return parsed;
}
function requireEmail(value) {
const cleaned = requireText(value, 'Email');
if (!emailPattern.test(cleaned)) {
throw createBadRequest('Please provide a valid email address.');
}
return cleaned;
}
function requirePhone(value) {
const cleaned = requireText(value, 'Phone number');
if (!phonePattern.test(cleaned)) {
throw createBadRequest('Please provide a valid phone number.');
}
return cleaned;
}
function buildApplicationNumber() {
const now = new Date();
const datePart = `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, '0')}${String(now.getUTCDate()).padStart(2, '0')}`;
const randomPart = Math.random().toString(36).slice(2, 6).toUpperCase();
return `JKM-${datePart}-${randomPart}`;
}
router.post('/apply', wrapAsync(async (req, res) => {
const payload = req.body?.data || req.body || {};
const fullName = requireText(payload.fullName, 'Full name');
const university = requireText(payload.university, 'University');
const phone = requirePhone(payload.phone);
const email = requireEmail(payload.email);
const purpose = requireText(payload.purpose, 'Purpose', 4);
const collateralItem = requireText(payload.collateralItem, 'Collateral item', 3);
const requestedAmount = requirePositiveNumber(payload.requestedAmount, 'Requested amount');
const collateralValue = requirePositiveNumber(payload.collateralValue, 'Collateral value');
const consent = Boolean(payload.consent);
if (!consent) {
throw createBadRequest('You must confirm that you understand the loan terms before submitting.');
}
if (collateralValue < requestedAmount * 2) {
throw createBadRequest('Collateral value must be at least 2× the requested loan amount.');
}
const submittedAt = new Date();
const applicationNumber = buildApplicationNumber();
const currentUser = { id: null };
const applicationSummary = `${purpose}${university}`;
const internalNote = [
`Applicant: ${fullName}`,
`University: ${university}`,
`Phone: ${phone}`,
`Email: ${email}`,
`Collateral item: ${collateralItem}`,
`Collateral value: ${collateralValue}`,
'Terms acknowledged: 2 weeks, 25% interest, collateral held as security.',
].join('\n');
const transaction = await db.sequelize.transaction();
try {
const loanApplication = await Loan_applicationsDBApi.create(
{
application_number: applicationNumber,
requested_amount: requestedAmount,
purpose: applicationSummary,
status: 'submitted',
submitted_at: submittedAt,
decision_note: internalNote,
},
{
currentUser,
transaction,
},
);
await Contact_messagesDBApi.create(
{
sender_name: fullName,
sender_email: email,
sender_phone: phone,
subject: 'loan_application_help',
message: [`Loan application reference: ${applicationNumber}`, internalNote].join('\n\n'),
status: 'new',
received_at: submittedAt,
},
{
currentUser,
transaction,
},
);
await transaction.commit();
res.status(200).send({
success: true,
reference: applicationNumber,
loanApplicationId: loanApplication.id,
message: 'Application received successfully.',
});
} catch (error) {
await transaction.rollback();
throw error;
}
}));
router.post('/contact', wrapAsync(async (req, res) => {
const payload = req.body?.data || req.body || {};
const senderName = requireText(payload.name, 'Name');
const senderEmail = requireEmail(payload.email);
const senderPhone = cleanText(payload.phone);
const message = requireText(payload.message, 'Message', 8);
const subject = cleanText(payload.subject) || 'other';
if (senderPhone && !phonePattern.test(senderPhone)) {
throw createBadRequest('Please provide a valid phone number.');
}
if (!CONTACT_SUBJECTS.has(subject)) {
throw createBadRequest('Please choose a valid subject.');
}
await Contact_messagesDBApi.create(
{
sender_name: senderName,
sender_email: senderEmail,
sender_phone: senderPhone || null,
subject,
message,
status: 'new',
received_at: new Date(),
},
{
currentUser: { id: null },
},
);
res.status(200).send({
success: true,
message: 'Message sent successfully.',
});
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -1,6 +1,5 @@
import React, {useEffect, useRef} from 'react' import React, { useEffect, useRef, useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider' import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon' import BaseIcon from './BaseIcon'

View File

@ -0,0 +1,113 @@
export const brand = {
name: 'JK Microfinance',
location: 'Dar es Salaam, Tanzania',
phone: '+255700123456',
email: 'info@jkmicrofinance.co.tz',
primary: '#0B1F4B',
accent: '#D4AF37',
mutedBg: '#F5F8FC',
headline: 'Quick Loans for University Students in Dar es Salaam',
subheadline: 'Get the cash you need in minutes — simple, fast, and reliable.',
};
export const navigationLinks = [
{ label: 'About', href: '#about' },
{ label: 'Loan Terms', href: '#loan-terms' },
{ label: 'How It Works', href: '#how-it-works' },
{ label: 'Testimonials', href: '#testimonials' },
{ label: 'Contact', href: '#contact' },
];
export const trustPoints = [
'Built for Dar es Salaam university students',
'Same-day review with clear 2-week repayment terms',
'Secure collateral handling and transparent pricing',
];
export const services = [
{
title: 'Student Emergency Loans',
description:
'Fast support for urgent school payments, medical needs, or family emergencies when timing matters most.',
},
{
title: 'Short-Term Cash Loans',
description:
'Simple 2-week loans designed for short gaps between allowances, support from home, or side-income payments.',
},
{
title: 'Quick Approval Loans',
description:
'A streamlined review process with student-friendly guidance so you can understand your terms before you commit.',
},
];
export const reasons = [
'Fast approval, often the same day',
'Student-focused support for university life',
'Simple and transparent requirements',
'Trusted, secure handling of collateral',
'Convenient for students based in Dar es Salaam',
];
export const processSteps = [
{
step: '1',
title: 'Apply for a loan',
description:
'Tell us how much you need, what it is for, and how we can reach you quickly.',
},
{
step: '2',
title: 'Provide collateral worth 2× the loan value',
description:
'We safely hold a valuable item as bond while your short-term loan is active.',
},
{
step: '3',
title: 'Receive your money quickly',
description:
'Once everything is confirmed, we arrange your funds without unnecessary delays.',
},
];
export const testimonials = [
{
name: 'Hawa Selemani',
university: 'University of Dar es Salaam',
quote:
'I was short on my registration payment and JK helped me cover it quickly with clear terms.',
},
{
name: 'Baraka Mwinuka',
university: 'Institute of Finance Management',
quote:
'I needed urgent cash for a family emergency and the process was straightforward and respectful.',
},
{
name: 'Rehema Salum',
university: 'Ardhi University',
quote:
'During exams I needed support for transport and meals. Approval was fast and the terms were clear.',
},
{
name: 'Elvis Mrema',
university: 'Muhimbili University of Health and Allied Sciences',
quote:
'Same-day support when I had clinical rotation costs. Communication was professional throughout.',
},
];
export const transparencyPoints = [
'Each loan runs for 2 weeks from the date you receive your funds.',
'A 25% interest charge applies for each loan period.',
'Collateral worth at least 2× the loan amount is required before disbursement.',
'Collateral is stored securely and is only used for recovery if repayment is delayed.',
];
export const campusImageQueries = [
'african university students',
'african campus life',
'student studying africa',
'young african professionals',
];

View File

@ -1,5 +1,4 @@
import React, { ReactNode, useEffect } from 'react' import React, { ReactNode, useEffect, useState } from 'react'
import { useState } from 'react'
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside' import menuAside from '../menuAside'

View File

@ -0,0 +1,443 @@
import {
mdiArrowLeft,
mdiCheckCircle,
mdiShieldCheck,
mdiTimerSand,
} from '@mdi/js';
import axios from 'axios';
import Head from 'next/head';
import Link from 'next/link';
import React, { ReactElement, useMemo, useState } from 'react';
import BaseButton from '../components/BaseButton';
import BaseIcon from '../components/BaseIcon';
import { getPageTitle } from '../config';
import { brand, transparencyPoints } from '../data/jkMicrofinance';
import LayoutGuest from '../layouts/Guest';
type ApplyFormState = {
fullName: string;
university: string;
phone: string;
email: string;
requestedAmount: string;
purpose: string;
collateralItem: string;
collateralValue: string;
consent: boolean;
};
type ApplyResponse = {
reference: string;
};
const initialFormState: ApplyFormState = {
fullName: '',
university: '',
phone: '',
email: '',
requestedAmount: '',
purpose: '',
collateralItem: '',
collateralValue: '',
consent: false,
};
const formatCurrency = (value: number) =>
new Intl.NumberFormat('en-TZ', {
style: 'currency',
currency: 'TZS',
maximumFractionDigits: 0,
}).format(value || 0);
function getErrorMessage(error: unknown) {
if (axios.isAxiosError(error)) {
if (typeof error.response?.data === 'string') {
return error.response.data;
}
return error.message;
}
if (error instanceof Error) {
return error.message;
}
return 'We could not send your application right now. Please try again.';
}
export default function ApplyPage() {
const [form, setForm] = useState<ApplyFormState>(initialFormState);
const [errorMessage, setErrorMessage] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [successData, setSuccessData] = useState<ApplyResponse | null>(null);
const requestedAmount = Number(form.requestedAmount) || 0;
const collateralValue = Number(form.collateralValue) || 0;
const repaymentAmount = useMemo(
() => requestedAmount * 1.25,
[requestedAmount],
);
const minimumCollateral = useMemo(
() => requestedAmount * 2,
[requestedAmount],
);
const updateField = (
event:
| React.ChangeEvent<HTMLInputElement>
| React.ChangeEvent<HTMLSelectElement>
| React.ChangeEvent<HTMLTextAreaElement>,
) => {
const target = event.target;
const { name, value } = target;
const nextValue =
target instanceof HTMLInputElement && target.type === 'checkbox'
? target.checked
: value;
setForm((current) => ({
...current,
[name]: nextValue,
}));
};
const validateForm = () => {
if (!form.fullName.trim()) return 'Please enter your full name.';
if (!form.university.trim()) return 'Please enter your university name.';
if (!form.phone.trim()) return 'Please enter your phone number.';
if (!form.email.trim()) return 'Please enter your email address.';
if (!requestedAmount || requestedAmount <= 0)
return 'Please enter the amount you want to borrow.';
if (!form.purpose.trim()) return 'Please tell us what the loan will help with.';
if (!form.collateralItem.trim())
return 'Please describe the collateral item you will provide.';
if (!collateralValue || collateralValue <= 0)
return 'Please enter the estimated value of your collateral.';
if (collateralValue < minimumCollateral)
return 'Your collateral must be worth at least 2× the requested amount.';
if (!form.consent)
return 'Please confirm that you understand the 2-week term, 25% interest, and collateral requirement.';
return '';
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const validationMessage = validateForm();
if (validationMessage) {
setErrorMessage(validationMessage);
return;
}
try {
setIsSubmitting(true);
setErrorMessage('');
const response = await axios.post('/public/forms/apply', {
data: form,
});
setSuccessData({ reference: response.data.reference });
setForm(initialFormState);
} catch (error: unknown) {
setErrorMessage(getErrorMessage(error));
} finally {
setIsSubmitting(false);
}
};
return (
<>
<Head>
<title>{getPageTitle('Apply for a student loan')}</title>
</Head>
<div className="min-h-screen bg-slate-950 text-slate-900">
<div className="border-b border-white/10 bg-slate-950/95 backdrop-blur">
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-4 sm:px-6 lg:px-8">
<Link
href="/"
className="flex items-center gap-3 text-sm font-semibold text-white transition hover:text-[#D4AF37]"
>
<BaseIcon path={mdiArrowLeft} size={18} />
Back to home
</Link>
<div className="flex items-center gap-3">
<BaseButton
href="/login"
color="whiteDark"
outline
label="Admin login"
className="border-white/20 text-white"
/>
</div>
</div>
</div>
<main className="relative overflow-hidden">
<div className="absolute inset-x-0 top-0 -z-10 h-[30rem] bg-[radial-gradient(circle_at_top,_rgba(212,175,55,0.18),_transparent_34%),linear-gradient(180deg,_#0B1F4B_0%,_#020617_70%)]" />
<div className="mx-auto grid max-w-7xl gap-8 px-4 py-10 sm:px-6 lg:grid-cols-[1.05fr_0.95fr] lg:px-8 lg:py-16">
<section className="space-y-6 text-white">
<span className="inline-flex items-center rounded-full border border-white/15 bg-white/10 px-4 py-1 text-sm font-medium text-slate-100">
Student-focused support in Dar es Salaam
</span>
<div className="space-y-4">
<h1 className="max-w-xl text-4xl font-black tracking-tight sm:text-5xl">
Apply in minutes and see your loan terms before you submit.
</h1>
<p className="max-w-2xl text-base leading-7 text-slate-200 sm:text-lg">
{brand.name} is built for university students who need short-term financial support without hidden terms.
Share your request, confirm the collateral details, and our team will follow up quickly.
</p>
</div>
<div className="grid gap-4 sm:grid-cols-3">
<div className="rounded-3xl border border-white/10 bg-white/10 p-5 shadow-xl shadow-black/10 backdrop-blur">
<div className="mb-3 flex h-12 w-12 items-center justify-center rounded-2xl bg-[#D4AF37]/20 text-[#F6D983]">
<BaseIcon path={mdiTimerSand} size={24} />
</div>
<p className="text-sm font-semibold text-white">2-week loan period</p>
<p className="mt-2 text-sm text-slate-300">Designed for urgent, short-term student needs.</p>
</div>
<div className="rounded-3xl border border-white/10 bg-white/10 p-5 shadow-xl shadow-black/10 backdrop-blur">
<div className="mb-3 flex h-12 w-12 items-center justify-center rounded-2xl bg-[#D4AF37]/20 text-[#F6D983]">
<BaseIcon path={mdiShieldCheck} size={24} />
</div>
<p className="text-sm font-semibold text-white">25% interest per loan</p>
<p className="mt-2 text-sm text-slate-300">The repayment amount is shown clearly before you apply.</p>
</div>
<div className="rounded-3xl border border-white/10 bg-white/10 p-5 shadow-xl shadow-black/10 backdrop-blur">
<div className="mb-3 flex h-12 w-12 items-center justify-center rounded-2xl bg-[#D4AF37]/20 text-[#F6D983]">
<BaseIcon path={mdiCheckCircle} size={24} />
</div>
<p className="text-sm font-semibold text-white">Collateral required</p>
<p className="mt-2 text-sm text-slate-300">Your item must be worth at least 2× the amount requested.</p>
</div>
</div>
<div className="rounded-[2rem] border border-white/10 bg-slate-900/70 p-6 shadow-2xl shadow-[#020617]/50">
<p className="text-sm font-semibold uppercase tracking-[0.2em] text-[#D4AF37]">Transparent terms</p>
<ul className="mt-4 space-y-3 text-sm leading-6 text-slate-200">
{transparencyPoints.map((point) => (
<li key={point} className="flex gap-3">
<span className="mt-1 h-2.5 w-2.5 rounded-full bg-[#D4AF37]" />
<span>{point}</span>
</li>
))}
</ul>
</div>
</section>
<section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-[0_30px_80px_rgba(2,6,23,0.28)] sm:p-8">
{successData ? (
<div className="flex h-full flex-col justify-center space-y-6">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-emerald-100 text-emerald-600">
<BaseIcon path={mdiCheckCircle} size={34} />
</div>
<div className="space-y-3">
<p className="text-sm font-semibold uppercase tracking-[0.2em] text-emerald-600">
Application received
</p>
<h2 className="text-3xl font-black tracking-tight text-slate-950">
Thank you weve recorded your request.
</h2>
<p className="text-base leading-7 text-slate-600">
Your reference is <span className="font-bold text-slate-950">{successData.reference}</span>. Keep it handy if you speak to our team.
</p>
</div>
<div className="rounded-3xl border border-slate-200 bg-slate-50 p-5 text-sm text-slate-700">
<p className="font-semibold text-slate-950">What happens next?</p>
<ul className="mt-3 space-y-2">
<li> Our team reviews your request and confirms your contact details.</li>
<li> We verify the collateral item and explain the repayment amount.</li>
<li> If everything is in order, we arrange the next step quickly.</li>
</ul>
</div>
<div className="flex flex-col gap-3 sm:flex-row">
<BaseButton href="/" color="info" label="Back to homepage" className="justify-center" />
<BaseButton
color="whiteDark"
outline
label="Submit another application"
className="justify-center"
onClick={() => setSuccessData(null)}
/>
</div>
</div>
) : (
<>
<div className="mb-6 space-y-2">
<p className="text-sm font-semibold uppercase tracking-[0.2em] text-[#0B1F4B]">
Start your application
</p>
<h2 className="text-3xl font-black tracking-tight text-slate-950">
Student loan request form
</h2>
<p className="text-sm leading-6 text-slate-600">
Fill in your details and well record your request with clear terms attached.
</p>
</div>
<form className="space-y-5" onSubmit={handleSubmit}>
<div className="grid gap-5 sm:grid-cols-2">
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">Full name</span>
<input
name="fullName"
value={form.fullName}
onChange={updateField}
placeholder="Your full name"
className="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">University</span>
<input
name="university"
value={form.university}
onChange={updateField}
placeholder="e.g. University of Dar es Salaam"
className="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
</div>
<div className="grid gap-5 sm:grid-cols-2">
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">Phone number</span>
<input
name="phone"
value={form.phone}
onChange={updateField}
placeholder="+255..."
className="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">Email address</span>
<input
name="email"
type="email"
value={form.email}
onChange={updateField}
placeholder="you@example.com"
className="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
</div>
<div className="grid gap-5 sm:grid-cols-2">
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">Requested amount (TZS)</span>
<input
name="requestedAmount"
type="number"
min="0"
value={form.requestedAmount}
onChange={updateField}
placeholder="e.g. 150000"
className="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">Estimated collateral value (TZS)</span>
<input
name="collateralValue"
type="number"
min="0"
value={form.collateralValue}
onChange={updateField}
placeholder="Must be at least 2× the loan amount"
className="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
</div>
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">What valuable item will you provide as collateral?</span>
<input
name="collateralItem"
value={form.collateralItem}
onChange={updateField}
placeholder="e.g. laptop, smartphone, camera"
className="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">What do you need the loan for?</span>
<textarea
name="purpose"
value={form.purpose}
onChange={updateField}
placeholder="School fees, transport, accommodation, emergency needs..."
className="min-h-[132px] w-full rounded-2xl border border-slate-200 px-4 py-3 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
<div className="rounded-3xl border border-[#D4AF37]/30 bg-[#FFF9E7] p-5">
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-[#8D6B12]">
Your loan summary
</p>
<div className="mt-4 grid gap-4 sm:grid-cols-3">
<div>
<p className="text-xs uppercase tracking-[0.16em] text-slate-500">Repayment in 2 weeks</p>
<p className="mt-2 text-lg font-bold text-slate-950">{formatCurrency(repaymentAmount)}</p>
</div>
<div>
<p className="text-xs uppercase tracking-[0.16em] text-slate-500">Minimum collateral value</p>
<p className="mt-2 text-lg font-bold text-slate-950">{formatCurrency(minimumCollateral)}</p>
</div>
<div>
<p className="text-xs uppercase tracking-[0.16em] text-slate-500">Interest applied</p>
<p className="mt-2 text-lg font-bold text-slate-950">25% per loan</p>
</div>
</div>
</div>
<label className="flex items-start gap-3 rounded-3xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700">
<input
type="checkbox"
name="consent"
checked={form.consent}
onChange={updateField}
className="mt-1 h-4 w-4 rounded border-slate-300 text-[#0B1F4B] focus:ring-[#D4AF37]"
/>
<span>
I understand the loan lasts 2 weeks, a 25% interest charge applies, and my collateral must be worth at least 2× the amount I want to borrow.
</span>
</label>
{errorMessage ? (
<div className="rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
{errorMessage}
</div>
) : null}
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-slate-500">
By submitting, youre asking our team to contact you and review your request.
</p>
<BaseButton
type="submit"
color="info"
label={isSubmitting ? 'Submitting...' : 'Submit application'}
disabled={isSubmitting}
className="justify-center px-8"
/>
</div>
</form>
</>
)}
</section>
</div>
</main>
</div>
</>
);
}
ApplyPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};

View File

@ -1,166 +1,842 @@
import {
import React, { useEffect, useState } from 'react'; mdiArrowRight,
import type { ReactElement } from 'react'; mdiCashFast,
mdiCellphone,
mdiCheckCircle,
mdiClockOutline,
mdiEmailOutline,
mdiMapMarkerOutline,
mdiMenu,
mdiPhoneOutline,
mdiShieldCheck,
mdiStarFourPoints,
mdiWalletOutline,
} from '@mdi/js';
import axios from 'axios';
import Head from 'next/head'; import Head from 'next/head';
import Link from 'next/link'; import Link from 'next/link';
import React, { ReactElement, useEffect, useState } from 'react';
import BaseButton from '../components/BaseButton'; import BaseButton from '../components/BaseButton';
import CardBox from '../components/CardBox'; import BaseIcon from '../components/BaseIcon';
import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
import BaseDivider from '../components/BaseDivider';
import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config'; import { getPageTitle } from '../config';
import { useAppSelector } from '../stores/hooks'; import {
import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; brand,
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; campusImageQueries,
navigationLinks,
processSteps,
reasons,
services,
testimonials,
transparencyPoints,
trustPoints,
} from '../data/jkMicrofinance';
import { getMultiplePexelsImages } from '../helpers/pexels';
import LayoutGuest from '../layouts/Guest';
type PexelsImage = {
src?: string;
photographer?: string;
photographer_url?: string;
};
export default function Starter() { type ContactFormState = {
const [illustrationImage, setIllustrationImage] = useState({ name: string;
src: undefined, email: string;
photographer: undefined, phone: string;
photographer_url: undefined, subject: string;
}) message: string;
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) };
const [contentType, setContentType] = useState('image');
const [contentPosition, setContentPosition] = useState('right');
const textColor = useAppSelector((state) => state.style.linkColor);
const title = 'JK Microfinance Website' const initialContactState: ContactFormState = {
name: '',
email: '',
phone: '',
subject: 'loan_terms',
message: '',
};
// Fetch Pexels image/video const contactSubjects = [
useEffect(() => { { value: 'loan_terms', label: 'Loan terms' },
async function fetchData() { { value: 'loan_application_help', label: 'Application help' },
const image = await getPexelsImage(); { value: 'collateral', label: 'Collateral' },
const video = await getPexelsVideo(); { value: 'repayment', label: 'Repayment' },
setIllustrationImage(image); { value: 'partnership', label: 'Partnership' },
setIllustrationVideo(video); { value: 'other', label: 'Other' },
];
const socialLinks = [
{ label: 'Instagram', href: '#' },
{ label: 'Facebook', href: '#' },
{ label: 'X', href: '#' },
];
const featureCards = [
{
icon: mdiClockOutline,
title: 'Fast approval',
description: 'Same-day or within-hours review so urgent needs do not become bigger problems.',
},
{
icon: mdiShieldCheck,
title: 'Straightforward process',
description: 'Clear terms, visible repayment amounts, and no hidden conditions during review.',
},
{
icon: mdiWalletOutline,
title: 'Student-first support',
description: 'Designed around the real cash-flow pressure students face during the academic term.',
},
];
function getErrorMessage(error: unknown) {
if (axios.isAxiosError(error)) {
if (typeof error.response?.data === 'string') {
return error.response.data;
} }
fetchData();
}, []);
const imageBlock = (image) => ( return error.message;
<div }
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
style={{
backgroundImage: `${
image
? `url(${image?.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}}
>
<div className='flex justify-center w-full bg-blue-300/20'>
<a
className='text-[8px]'
href={image?.photographer_url}
target='_blank'
rel='noreferrer'
>
Photo by {image?.photographer} on Pexels
</a>
</div>
</div>
);
const videoBlock = (video) => { if (error instanceof Error) {
if (video?.video_files?.length > 0) { return error.message;
return ( }
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
<video return 'Something went wrong. Please try again.';
className='absolute top-0 left-0 w-full h-full object-cover' }
autoPlay
loop export default function HomePage() {
muted const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
> const [campusImages, setCampusImages] = useState<PexelsImage[]>([]);
<source src={video?.video_files[0]?.link} type='video/mp4'/> const [contactForm, setContactForm] = useState<ContactFormState>(initialContactState);
Your browser does not support the video tag. const [contactError, setContactError] = useState('');
</video> const [contactSuccess, setContactSuccess] = useState('');
<div className='flex justify-center w-full bg-blue-300/20 z-10'> const [isSendingContact, setIsSendingContact] = useState(false);
<a
className='text-[8px]' useEffect(() => {
href={video?.user?.url} let isMounted = true;
target='_blank'
rel='noreferrer' const loadImages = async () => {
> const images = await getMultiplePexelsImages(campusImageQueries);
Video by {video.user.name} on Pexels
</a> if (isMounted && Array.isArray(images)) {
</div> setCampusImages(images.filter(Boolean));
</div>)
} }
}; };
loadImages();
return () => {
isMounted = false;
};
}, []);
const updateContactField = (
event:
| React.ChangeEvent<HTMLInputElement>
| React.ChangeEvent<HTMLTextAreaElement>
| React.ChangeEvent<HTMLSelectElement>,
) => {
const { name, value } = event.target;
setContactForm((current) => ({
...current,
[name]: value,
}));
};
const submitContactForm = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!contactForm.name.trim()) {
setContactError('Please enter your name.');
return;
}
if (!contactForm.email.trim()) {
setContactError('Please enter your email address.');
return;
}
if (!contactForm.message.trim()) {
setContactError('Please add a short message so we know how to help.');
return;
}
try {
setIsSendingContact(true);
setContactError('');
setContactSuccess('');
await axios.post('/public/forms/contact', {
data: contactForm,
});
setContactSuccess('Thanks — your message has been sent. Our team will get back to you soon.');
setContactForm(initialContactState);
} catch (error: unknown) {
setContactError(getErrorMessage(error));
} finally {
setIsSendingContact(false);
}
};
const heroImage = campusImages[0];
const supportingImages = campusImages.slice(1, 4);
return ( return (
<div <>
style={
contentPosition === 'background'
? {
backgroundImage: `${
illustrationImage
? `url(${illustrationImage.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}
: {}
}
>
<Head> <Head>
<title>{getPageTitle('Starter Page')}</title> <title>{getPageTitle('JK Microfinance')}</title>
<meta
name="description"
content="JK Microfinance offers transparent short-term loans for university students in Dar es Salaam, Tanzania."
/>
</Head> </Head>
<SectionFullScreen bg='violet'> <div className="bg-slate-950 text-slate-900">
<div <header className="sticky top-0 z-50 border-b border-white/10 bg-slate-950/90 backdrop-blur-xl">
className={`flex ${ <div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-4 sm:px-6 lg:px-8">
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row' <Link href="/" className="flex items-center gap-3 text-white">
} min-h-screen w-full`} <span className="flex h-11 w-11 items-center justify-center rounded-2xl bg-[#D4AF37] text-sm font-black text-[#0B1F4B]">
JK
</span>
<div>
<p className="text-sm font-semibold uppercase tracking-[0.2em] text-[#D4AF37]">JK Microfinance</p>
<p className="text-xs text-slate-300">Dar es Salaam student loans</p>
</div>
</Link>
<nav className="hidden items-center gap-6 lg:flex">
{navigationLinks.map((link) => (
<a
key={link.href}
href={link.href}
className="text-sm font-medium text-slate-200 transition hover:text-[#D4AF37]"
> >
{contentType === 'image' && contentPosition !== 'background' {link.label}
? imageBlock(illustrationImage) </a>
: null} ))}
{contentType === 'video' && contentPosition !== 'background' <Link href="/login" className="text-sm font-medium text-slate-200 transition hover:text-[#D4AF37]">
? videoBlock(illustrationVideo) Admin login
: null} </Link>
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'> <BaseButton href="/apply" color="info" label="Apply Now" className="justify-center" />
<CardBox className='w-full md:w-3/5 lg:w-2/3'> </nav>
<CardBoxComponentTitle title="Welcome to your JK Microfinance Website app!"/>
<div className="space-y-3"> <button
<p className='text-center text-gray-500'>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p> type="button"
<p className='text-center text-gray-500'>For guides and documentation please check aria-label="Open navigation"
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p> onClick={() => setIsMobileMenuOpen((current) => !current)}
className="inline-flex h-11 w-11 items-center justify-center rounded-2xl border border-white/10 text-white lg:hidden"
>
<BaseIcon path={mdiMenu} size={22} />
</button>
</div> </div>
<BaseButtons> {isMobileMenuOpen ? (
<BaseButton <div className="border-t border-white/10 bg-slate-950 px-4 py-4 lg:hidden">
href='/login' <div className="flex flex-col gap-3">
label='Login' {navigationLinks.map((link) => (
color='info' <a
className='w-full' key={link.href}
/> href={link.href}
className="rounded-2xl px-4 py-3 text-sm font-medium text-slate-100 transition hover:bg-white/5"
</BaseButtons> onClick={() => setIsMobileMenuOpen(false)}
</CardBox> >
</div> {link.label}
</div> </a>
</SectionFullScreen> ))}
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'> <Link
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p> href="/login"
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'> className="rounded-2xl px-4 py-3 text-sm font-medium text-slate-100 transition hover:bg-white/5"
Privacy Policy onClick={() => setIsMobileMenuOpen(false)}
>
Admin login
</Link>
<Link
href="/apply"
className="rounded-2xl bg-[#D4AF37] px-4 py-3 text-center text-sm font-bold text-[#0B1F4B]"
onClick={() => setIsMobileMenuOpen(false)}
>
Apply Now
</Link> </Link>
</div> </div>
</div> </div>
) : null}
</header>
<main>
<section className="relative overflow-hidden bg-[linear-gradient(180deg,_#0B1F4B_0%,_#07142F_55%,_#020617_100%)] text-white">
<div className="absolute inset-x-0 top-0 h-[34rem] bg-[radial-gradient(circle_at_top_left,_rgba(212,175,55,0.18),_transparent_32%),radial-gradient(circle_at_top_right,_rgba(59,130,246,0.24),_transparent_28%)]" />
<div className="mx-auto grid max-w-7xl gap-10 px-4 py-14 sm:px-6 lg:grid-cols-[1.02fr_0.98fr] lg:px-8 lg:py-20">
<div className="relative z-10 space-y-8">
<span className="inline-flex items-center rounded-full border border-white/15 bg-white/10 px-4 py-1.5 text-sm font-medium text-slate-100 backdrop-blur">
Trusted short-term financing for Dar es Salaam university students
</span>
<div className="space-y-5">
<h1 className="max-w-3xl text-4xl font-black tracking-tight sm:text-5xl lg:text-6xl">
{brand.headline}
</h1>
<p className="max-w-2xl text-base leading-8 text-slate-200 sm:text-lg">
{brand.subheadline} We help students handle urgent school and living expenses with clear terms, quick review, and responsible lending.
</p>
</div>
<div className="flex flex-col gap-3 sm:flex-row">
<BaseButton href="/apply" color="info" label="Apply Now" className="justify-center px-7 text-sm font-semibold" />
<a
href="#loan-terms"
className="inline-flex items-center justify-center rounded-full border border-white/20 px-6 py-3 text-sm font-semibold text-white transition hover:border-[#D4AF37] hover:text-[#D4AF37]"
>
Check Loan Terms
</a>
</div>
<div className="grid gap-4 sm:grid-cols-3">
{featureCards.map((card) => (
<div
key={card.title}
className="rounded-[1.75rem] border border-white/10 bg-white/10 p-5 shadow-xl shadow-black/10 backdrop-blur transition duration-300 hover:-translate-y-1 hover:bg-white/12"
>
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-[#D4AF37]/20 text-[#F6D983]">
<BaseIcon path={card.icon} size={24} />
</div>
<h2 className="text-lg font-bold text-white">{card.title}</h2>
<p className="mt-2 text-sm leading-6 text-slate-300">{card.description}</p>
</div>
))}
</div>
</div>
<div className="relative z-10">
<div className="grid gap-4 sm:grid-cols-[1.08fr_0.92fr]">
<div className="relative min-h-[25rem] overflow-hidden rounded-[2rem] border border-white/10 bg-white/10 shadow-[0_30px_80px_rgba(2,6,23,0.35)] backdrop-blur">
{heroImage?.src ? (
<img
src={heroImage.src}
alt="African university students"
className="h-full w-full object-cover"
/>
) : (
<div className="h-full w-full bg-[linear-gradient(135deg,_rgba(212,175,55,0.4),_rgba(11,31,75,0.92))]" />
)}
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-slate-950/90 via-slate-950/45 to-transparent p-6">
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-[#D4AF37]">Quick support</p>
<p className="mt-2 max-w-sm text-xl font-bold text-white">
Built around school fees, emergency needs, transport, and day-to-day student pressures.
</p>
</div>
</div>
<div className="grid gap-4">
{supportingImages.length > 0
? supportingImages.map((image, index) => (
<div
key={`${image.src}-${index}`}
className="relative min-h-[11.5rem] overflow-hidden rounded-[1.75rem] border border-white/10 bg-white/10 shadow-xl shadow-black/10 backdrop-blur"
>
{image?.src ? (
<img
src={image.src}
alt="Campus life"
className="h-full w-full object-cover"
/>
) : (
<div className="h-full w-full bg-[linear-gradient(135deg,_rgba(212,175,55,0.25),_rgba(15,23,42,0.85))]" />
)}
<div className="absolute inset-0 bg-gradient-to-t from-slate-950/75 via-transparent to-transparent" />
</div>
))
: Array.from({ length: 3 }).map((_, index) => (
<div
key={index}
className="min-h-[11.5rem] rounded-[1.75rem] border border-white/10 bg-[linear-gradient(135deg,_rgba(212,175,55,0.18),_rgba(15,23,42,0.88))]"
/>
))}
</div>
</div>
<div className="mt-5 grid gap-4 sm:grid-cols-3">
<div className="rounded-[1.5rem] border border-white/10 bg-slate-900/60 p-5 backdrop-blur">
<p className="text-xs uppercase tracking-[0.18em] text-[#D4AF37]">Loan duration</p>
<p className="mt-2 text-2xl font-black text-white">2 weeks</p>
</div>
<div className="rounded-[1.5rem] border border-white/10 bg-slate-900/60 p-5 backdrop-blur">
<p className="text-xs uppercase tracking-[0.18em] text-[#D4AF37]">Interest rate</p>
<p className="mt-2 text-2xl font-black text-white">25% per loan</p>
</div>
<div className="rounded-[1.5rem] border border-white/10 bg-slate-900/60 p-5 backdrop-blur">
<p className="text-xs uppercase tracking-[0.18em] text-[#D4AF37]">Collateral</p>
<p className="mt-2 text-2xl font-black text-white">2× loan value</p>
</div>
</div>
</div>
</div>
</section>
<section id="about" className="bg-white py-16 sm:py-20">
<div className="mx-auto grid max-w-7xl gap-10 px-4 sm:px-6 lg:grid-cols-[0.9fr_1.1fr] lg:px-8">
<div className="space-y-5">
<span className="inline-flex rounded-full bg-[#EFF4FF] px-4 py-1.5 text-sm font-semibold text-[#0B1F4B]">
About JK Microfinance
</span>
<h2 className="text-3xl font-black tracking-tight text-slate-950 sm:text-4xl">
Helping students move forward when short-term cash pressure hits.
</h2>
<p className="text-base leading-8 text-slate-600">
JK Microfinance supports university students with quick financial solutions so they can stay focused on school, emergencies, and daily responsibilities.
</p>
<div className="rounded-[1.75rem] border border-slate-200 bg-slate-50 p-6">
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-[#0B1F4B]">Mission</p>
<p className="mt-2 text-base leading-7 text-slate-600">
Help students overcome short-term financial challenges with fast, clear, and responsible support.
</p>
<p className="mt-5 text-sm font-semibold uppercase tracking-[0.18em] text-[#0B1F4B]">Vision</p>
<p className="mt-2 text-base leading-7 text-slate-600">
Empower youth in Dar es Salaam through accessible finance that respects their goals and realities.
</p>
</div>
</div>
<div className="grid gap-5 sm:grid-cols-2">
{trustPoints.map((point) => (
<div
key={point}
className="rounded-[1.75rem] border border-slate-200 bg-white p-6 shadow-[0_24px_60px_rgba(15,23,42,0.08)]"
>
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-[#0B1F4B] text-white">
<BaseIcon path={mdiStarFourPoints} size={22} />
</div>
<p className="text-base font-semibold leading-7 text-slate-900">{point}</p>
</div>
))}
<div className="rounded-[1.75rem] bg-[#0B1F4B] p-6 text-white shadow-[0_24px_60px_rgba(11,31,75,0.28)] sm:col-span-2">
<div className="grid gap-6 sm:grid-cols-3">
<div>
<p className="text-xs uppercase tracking-[0.18em] text-[#D4AF37]">Focused market</p>
<p className="mt-2 text-2xl font-black">Dar es Salaam students</p>
</div>
<div>
<p className="text-xs uppercase tracking-[0.18em] text-[#D4AF37]">Loan promise</p>
<p className="mt-2 text-2xl font-black">Simple, fast, reliable</p>
</div>
<div>
<p className="text-xs uppercase tracking-[0.18em] text-[#D4AF37]">Trust signal</p>
<p className="mt-2 text-2xl font-black">Transparent terms first</p>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="loan-terms" className="bg-slate-50 py-16 sm:py-20">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-10 max-w-3xl">
<span className="inline-flex rounded-full bg-[#FFF4CF] px-4 py-1.5 text-sm font-semibold text-[#8D6B12]">
Important loan details
</span>
<h2 className="mt-4 text-3xl font-black tracking-tight text-slate-950 sm:text-4xl">
Transparent loan terms you can understand in one minute.
</h2>
<p className="mt-4 text-base leading-8 text-slate-600">
JK Microfinance is clear about how the loan works before you apply. No hidden terms, no confusing language.
</p>
</div>
<div className="grid gap-6 lg:grid-cols-[1.15fr_0.85fr]">
<div className="rounded-[2rem] border border-slate-200 bg-white p-7 shadow-[0_28px_80px_rgba(15,23,42,0.08)]">
<div className="grid gap-4 sm:grid-cols-2">
<div className="rounded-[1.5rem] bg-[#0B1F4B] p-6 text-white">
<p className="text-xs uppercase tracking-[0.18em] text-[#D4AF37]">Loan duration</p>
<p className="mt-2 text-3xl font-black">2 weeks</p>
</div>
<div className="rounded-[1.5rem] bg-[#D4AF37] p-6 text-[#0B1F4B]">
<p className="text-xs uppercase tracking-[0.18em]">Interest rate</p>
<p className="mt-2 text-3xl font-black">25% per loan</p>
</div>
<div className="rounded-[1.5rem] border border-slate-200 p-6">
<p className="text-xs uppercase tracking-[0.18em] text-slate-500">Target group</p>
<p className="mt-2 text-xl font-bold text-slate-950">University students in Dar es Salaam</p>
</div>
<div className="rounded-[1.5rem] border border-slate-200 p-6">
<p className="text-xs uppercase tracking-[0.18em] text-slate-500">Collateral rule</p>
<p className="mt-2 text-xl font-bold text-slate-950">Item worth at least 2× your loan amount</p>
</div>
</div>
<div className="mt-6 rounded-[1.5rem] border border-[#D4AF37]/40 bg-[#FFF9E7] p-6">
<p className="text-lg font-bold text-slate-950">Collateral is required and held securely as bond.</p>
<p className="mt-3 text-base leading-7 text-slate-700">
The item is used as security if repayment is delayed. We explain this requirement clearly and professionally before final approval.
</p>
</div>
</div>
<div className="rounded-[2rem] bg-[#0B1F4B] p-7 text-white shadow-[0_28px_80px_rgba(11,31,75,0.28)]">
<p className="text-sm font-semibold uppercase tracking-[0.2em] text-[#D4AF37]">Why this matters</p>
<ul className="mt-6 space-y-4">
{transparencyPoints.map((point) => (
<li key={point} className="flex gap-3 text-sm leading-6 text-slate-200">
<span className="mt-2 h-2.5 w-2.5 rounded-full bg-[#D4AF37]" />
<span>{point}</span>
</li>
))}
</ul>
<div className="mt-8 rounded-[1.5rem] border border-white/10 bg-white/10 p-5 backdrop-blur">
<p className="text-sm font-semibold text-white">Need to check your repayment first?</p>
<p className="mt-2 text-sm leading-6 text-slate-300">
Open the application page to estimate your 2-week repayment amount and minimum collateral value before you submit.
</p>
<div className="mt-5">
<BaseButton href="/apply" color="info" label="Open the application form" className="justify-center" />
</div>
</div>
</div>
</div>
</div>
</section>
<section className="bg-white py-16 sm:py-20">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-10 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div className="max-w-2xl">
<span className="inline-flex rounded-full bg-[#EFF4FF] px-4 py-1.5 text-sm font-semibold text-[#0B1F4B]">
Our services
</span>
<h2 className="mt-4 text-3xl font-black tracking-tight text-slate-950 sm:text-4xl">
Financial support built around common student needs.
</h2>
</div>
<BaseButton href="/apply" color="info" label="Apply Now" className="justify-center" />
</div>
<div className="grid gap-6 lg:grid-cols-3">
{services.map((service, index) => (
<div
key={service.title}
className="group rounded-[2rem] border border-slate-200 bg-white p-7 shadow-[0_24px_60px_rgba(15,23,42,0.08)] transition duration-300 hover:-translate-y-1 hover:shadow-[0_26px_80px_rgba(15,23,42,0.12)]"
>
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-slate-100 text-[#0B1F4B] transition group-hover:bg-[#0B1F4B] group-hover:text-white">
<BaseIcon
path={index === 0 ? mdiCashFast : index === 1 ? mdiWalletOutline : mdiCheckCircle}
size={26}
/>
</div>
<h3 className="mt-6 text-2xl font-bold text-slate-950">{service.title}</h3>
<p className="mt-4 text-base leading-7 text-slate-600">{service.description}</p>
<div className="mt-6 flex items-center gap-2 text-sm font-semibold text-[#0B1F4B]">
<Link href="/apply" className="inline-flex items-center gap-2 transition hover:text-[#D4AF37]">
Apply Now
<BaseIcon path={mdiArrowRight} size={18} />
</Link>
</div>
</div>
))}
</div>
</div>
</section>
<section className="bg-slate-50 py-16 sm:py-20">
<div className="mx-auto grid max-w-7xl gap-10 px-4 sm:px-6 lg:grid-cols-[0.95fr_1.05fr] lg:px-8">
<div>
<span className="inline-flex rounded-full bg-[#EFF4FF] px-4 py-1.5 text-sm font-semibold text-[#0B1F4B]">
Why choose us
</span>
<h2 className="mt-4 text-3xl font-black tracking-tight text-slate-950 sm:text-4xl">
Responsible, local, and student-focused from start to finish.
</h2>
<p className="mt-4 text-base leading-8 text-slate-600">
We keep the process simple and direct so students can decide with confidence.
</p>
</div>
<div className="grid gap-4 sm:grid-cols-2">
{reasons.map((reason) => (
<div key={reason} className="rounded-[1.75rem] border border-slate-200 bg-white p-6 shadow-[0_20px_50px_rgba(15,23,42,0.07)]">
<div className="mb-4 flex h-11 w-11 items-center justify-center rounded-2xl bg-[#FFF4CF] text-[#8D6B12]">
<BaseIcon path={mdiCheckCircle} size={22} />
</div>
<p className="text-base font-semibold leading-7 text-slate-900">{reason}</p>
</div>
))}
</div>
</div>
</section>
<section id="how-it-works" className="bg-[#0B1F4B] py-16 text-white sm:py-20">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-10 max-w-2xl">
<span className="inline-flex rounded-full bg-white/10 px-4 py-1.5 text-sm font-semibold text-[#F6D983]">
How it works
</span>
<h2 className="mt-4 text-3xl font-black tracking-tight sm:text-4xl">
A simple 3-step path from request to support.
</h2>
</div>
<div className="grid gap-6 lg:grid-cols-3">
{processSteps.map((step) => (
<div key={step.step} className="rounded-[2rem] border border-white/10 bg-white/10 p-7 backdrop-blur">
<span className="inline-flex h-12 w-12 items-center justify-center rounded-full bg-[#D4AF37] text-lg font-black text-[#0B1F4B]">
{step.step}
</span>
<h3 className="mt-6 text-2xl font-bold text-white">{step.title}</h3>
<p className="mt-4 text-base leading-7 text-slate-200">{step.description}</p>
</div>
))}
</div>
</div>
</section>
<section className="bg-white py-16 sm:py-20">
<div className="mx-auto grid max-w-7xl gap-6 px-4 sm:px-6 lg:grid-cols-[0.85fr_1.15fr] lg:px-8">
<div>
<span className="inline-flex rounded-full bg-[#FFF4CF] px-4 py-1.5 text-sm font-semibold text-[#8D6B12]">
Terms & transparency
</span>
<h2 className="mt-4 text-3xl font-black tracking-tight text-slate-950 sm:text-4xl">
Clear and professional, so you know exactly what to expect.
</h2>
</div>
<div className="rounded-[2rem] border border-slate-200 bg-slate-50 p-7 shadow-[0_24px_60px_rgba(15,23,42,0.06)]">
<ul className="space-y-4">
{transparencyPoints.map((point) => (
<li key={point} className="flex gap-3 text-base leading-7 text-slate-700">
<span className="mt-2 h-2.5 w-2.5 rounded-full bg-[#D4AF37]" />
<span>{point}</span>
</li>
))}
</ul>
</div>
</div>
</section>
<section id="testimonials" className="bg-slate-50 py-16 sm:py-20">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-10 max-w-2xl">
<span className="inline-flex rounded-full bg-[#EFF4FF] px-4 py-1.5 text-sm font-semibold text-[#0B1F4B]">
Student testimonials
</span>
<h2 className="mt-4 text-3xl font-black tracking-tight text-slate-950 sm:text-4xl">
Real student stories that speak to trust, speed, and clarity.
</h2>
</div>
<div className="grid gap-6 lg:grid-cols-2 xl:grid-cols-4">
{testimonials.map((testimonial) => (
<div key={testimonial.name} className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-[0_20px_50px_rgba(15,23,42,0.07)]">
<div className="mb-5 flex items-center gap-1 text-[#D4AF37]">
{Array.from({ length: 5 }).map((_, index) => (
<BaseIcon key={index} path={mdiStarFourPoints} size={16} />
))}
</div>
<p className="text-base leading-7 text-slate-700">{testimonial.quote}</p>
<div className="mt-6 border-t border-slate-100 pt-4">
<p className="font-bold text-slate-950">{testimonial.name}</p>
<p className="text-sm text-slate-500">{testimonial.university}</p>
</div>
</div>
))}
</div>
</div>
</section>
<section className="bg-[linear-gradient(135deg,_#0B1F4B_0%,_#102A69_60%,_#1E3A8A_100%)] py-16 text-white sm:py-20">
<div className="mx-auto flex max-w-7xl flex-col items-start justify-between gap-8 px-4 sm:px-6 lg:flex-row lg:items-center lg:px-8">
<div className="max-w-2xl">
<p className="text-sm font-semibold uppercase tracking-[0.2em] text-[#D4AF37]">Ready to act?</p>
<h2 className="mt-3 text-3xl font-black tracking-tight sm:text-4xl">
Need quick cash for school or emergencies?
</h2>
<p className="mt-4 text-base leading-8 text-slate-200">
Review the terms, calculate your repayment, and send your application in just a few minutes.
</p>
</div>
<BaseButton href="/apply" color="info" label="Apply Now" className="justify-center px-8" />
</div>
</section>
<section id="contact" className="bg-white py-16 sm:py-20">
<div className="mx-auto grid max-w-7xl gap-10 px-4 sm:px-6 lg:grid-cols-[0.9fr_1.1fr] lg:px-8">
<div>
<span className="inline-flex rounded-full bg-[#EFF4FF] px-4 py-1.5 text-sm font-semibold text-[#0B1F4B]">
Contact us
</span>
<h2 className="mt-4 text-3xl font-black tracking-tight text-slate-950 sm:text-4xl">
Speak with JK Microfinance in Dar es Salaam.
</h2>
<p className="mt-4 text-base leading-8 text-slate-600">
Have a question about loan terms, collateral, or the next step? Send us a message and our team will follow up.
</p>
<div className="mt-8 grid gap-4">
<div className="flex items-start gap-4 rounded-[1.75rem] border border-slate-200 bg-slate-50 p-5">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-[#0B1F4B] text-white">
<BaseIcon path={mdiMapMarkerOutline} size={24} />
</div>
<div>
<p className="font-semibold text-slate-950">Location</p>
<p className="mt-1 text-sm text-slate-600">{brand.location}</p>
</div>
</div>
<div className="flex items-start gap-4 rounded-[1.75rem] border border-slate-200 bg-slate-50 p-5">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-[#0B1F4B] text-white">
<BaseIcon path={mdiPhoneOutline} size={24} />
</div>
<div>
<p className="font-semibold text-slate-950">Phone number</p>
<a href={`tel:${brand.phone}`} className="mt-1 block text-sm text-slate-600 hover:text-[#0B1F4B]">
{brand.phone}
</a>
</div>
</div>
<div className="flex items-start gap-4 rounded-[1.75rem] border border-slate-200 bg-slate-50 p-5">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-[#0B1F4B] text-white">
<BaseIcon path={mdiEmailOutline} size={24} />
</div>
<div>
<p className="font-semibold text-slate-950">Email</p>
<a href={`mailto:${brand.email}`} className="mt-1 block text-sm text-slate-600 hover:text-[#0B1F4B]">
{brand.email}
</a>
</div>
</div>
</div>
</div>
<div className="rounded-[2rem] border border-slate-200 bg-white p-7 shadow-[0_28px_80px_rgba(15,23,42,0.08)]">
<form className="space-y-5" onSubmit={submitContactForm}>
<div className="grid gap-5 sm:grid-cols-2">
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">Name</span>
<input
name="name"
value={contactForm.name}
onChange={updateContactField}
placeholder="Your name"
className="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">Email</span>
<input
name="email"
type="email"
value={contactForm.email}
onChange={updateContactField}
placeholder="you@example.com"
className="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
</div>
<div className="grid gap-5 sm:grid-cols-2">
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">Phone</span>
<div className="relative">
<span className="pointer-events-none absolute inset-y-0 left-4 flex items-center text-slate-400">
<BaseIcon path={mdiCellphone} size={18} />
</span>
<input
name="phone"
value={contactForm.phone}
onChange={updateContactField}
placeholder="Optional"
className="h-12 w-full rounded-2xl border border-slate-200 pl-11 pr-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</div>
</label>
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">Subject</span>
<select
name="subject"
value={contactForm.subject}
onChange={updateContactField}
className="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
>
{contactSubjects.map((subject) => (
<option key={subject.value} value={subject.value}>
{subject.label}
</option>
))}
</select>
</label>
</div>
<label className="space-y-2">
<span className="text-sm font-semibold text-slate-800">Message</span>
<textarea
name="message"
value={contactForm.message}
onChange={updateContactField}
placeholder="Tell us what you need help with..."
className="min-h-[150px] w-full rounded-2xl border border-slate-200 px-4 py-3 text-sm outline-none transition focus:border-[#0B1F4B] focus:ring-2 focus:ring-[#D4AF37]/40"
/>
</label>
{contactError ? (
<div className="rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
{contactError}
</div>
) : null}
{contactSuccess ? (
<div className="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700">
{contactSuccess}
</div>
) : null}
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-slate-500">We keep communication clear, respectful, and straightforward.</p>
<BaseButton
type="submit"
color="info"
label={isSendingContact ? 'Sending...' : 'Send message'}
disabled={isSendingContact}
className="justify-center px-8"
/>
</div>
</form>
</div>
</div>
</section>
</main>
<footer className="border-t border-white/10 bg-slate-950 py-10 text-slate-300">
<div className="mx-auto grid max-w-7xl gap-8 px-4 sm:px-6 lg:grid-cols-[1fr_auto_auto] lg:px-8">
<div>
<p className="text-lg font-bold text-white">JK Microfinance</p>
<p className="mt-3 max-w-md text-sm leading-7 text-slate-400">
Quick, transparent student loans in Dar es Salaam with clear terms, strong support, and a modern application experience.
</p>
</div>
<div>
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-[#D4AF37]">Navigation</p>
<div className="mt-4 flex flex-col gap-3 text-sm">
{navigationLinks.map((link) => (
<a key={link.href} href={link.href} className="transition hover:text-white">
{link.label}
</a>
))}
<Link href="/apply" className="transition hover:text-white">
Apply Now
</Link>
<Link href="/login" className="transition hover:text-white">
Admin login
</Link>
</div>
</div>
<div>
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-[#D4AF37]">Social</p>
<div className="mt-4 flex flex-col gap-3 text-sm">
{socialLinks.map((link) => (
<a key={link.label} href={link.href} className="transition hover:text-white">
{link.label}
</a>
))}
</div>
</div>
</div>
<div className="mx-auto mt-8 max-w-7xl border-t border-white/10 px-4 pt-6 text-sm text-slate-500 sm:px-6 lg:px-8">
© 2026 JK Microfinance. All rights reserved.
</div>
</footer>
</div>
</>
); );
} }
Starter.getLayout = function getLayout(page: ReactElement) { HomePage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>; return <LayoutGuest>{page}</LayoutGuest>;
}; };