Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cdc49d576 |
@ -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');
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
203
backend/src/routes/public_forms.js
Normal file
203
backend/src/routes/public_forms.js
Normal 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;
|
||||||
@ -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'
|
||||||
|
|||||||
113
frontend/src/data/jkMicrofinance.ts
Normal file
113
frontend/src/data/jkMicrofinance.ts
Normal 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',
|
||||||
|
];
|
||||||
@ -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'
|
||||||
|
|||||||
443
frontend/src/pages/apply.tsx
Normal file
443
frontend/src/pages/apply.tsx
Normal 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 — we’ve 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 we’ll 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, you’re 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>;
|
||||||
|
};
|
||||||
@ -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>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user