Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,73 @@ const express = require("express");
const app = express();
const PORT = process.env.PORT || 4000;

let todos = [
];
let id_counter = 1;

app.use(express.json());
// Basic route
app.get("/", (req, res) => {
res.send("Hello from Express!");
});

app.get("/api/todos", (req, res) => {
res.json(todos);
});

app.get("/api/todos/:id", (req, res) => {
const todoId = parseInt(req.params.id);
if (Number.isNaN(todoId)) {
return res.status(400).json({ message: "Invalid ID" });
}
if (!todos.some((todo) => todo.id === todoId)) {
return res.status(404).json({ message: "Todo not found" });
}
res.json(todos.find((todo) => todo.id === todoId));
});

app.post("/api/todos", (req, res) => {
if (!req.body.title) {
return res.status(400).json({ message: "Title is required" });
}
const newTodo = { id: id_counter++, title: req.body.title, completed: false, createdAt: new Date() };
todos.push(newTodo);
res.status(201).json(newTodo);
});

app.put("/api/todos/:id", (req, res) => {
const todoId = parseInt(req.params.id);
if (Number.isNaN(todoId)) {
return res.status(400).json({ message: "Invalid ID" });
}
if (req.body.completed !== undefined && typeof req.body.completed !== "boolean") {
return res.status(400).json({ message: "completed must be a boolean" });
}
const todo = todos.find((t) => t.id === todoId);

if (todo) {
todo.title = req.body.title !== undefined ? req.body.title : todo.title;
todo.completed = req.body.completed !== undefined ? req.body.completed : todo.completed;
res.json(todo);
} else {
res.status(404).json({ message: "Todo not found" });
}
});

app.delete("/api/todos/:id", (req, res) => {
const todoId = parseInt(req.params.id);
if (Number.isNaN(todoId)) {
return res.status(400).json({ message: "Invalid ID" });
}
const index = todos.findIndex((t) => t.id === todoId);
if (index !== -1) {
todos.splice(index, 1);
return res.status(204).send();
} else {
res.status(404).json({ message: "Todo not found" });
}
});

// Start server
app.listen(PORT, () => {
console.log(`Backend is running on http://localhost:${PORT}`);
Expand Down
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"start": "node index.js"
},
"dependencies": {
"express": "^4.18.2"
"express": "^4.22.1"
}
}
22 changes: 16 additions & 6 deletions frontend/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,35 @@

:root {
--background: #ffffff;
--foreground: #171717;
--foreground: #1f2937; /* dark gray */

--primary: #2563eb; /* moola-style blue */
--primary-hover: #1d4ed8;

--muted: #6b7280; /* secondary text */
--border: #e5e7eb;
--card: #f9fafb;
}


@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-primary: var(--primary);
--color-border: var(--border);
--color-card: var(--card);
}


@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
--background: #ffffff;
--foreground: #171717;
}
}

body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
font-family: var(--font-sans), system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}
4 changes: 2 additions & 2 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
});

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Moola-inspired Landing Page",
description: "Frontend assessment landing page (hero + section, responsive).",
};

export default function RootLayout({
Expand Down
222 changes: 126 additions & 96 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,103 +1,133 @@
import Image from "next/image";

export default function Home() {
const deals = [
{ name: "Amazon.ca", bonus: null },
{ name: "DoorDash", bonus: "1.5% More" },
{ name: "Starbucks", bonus: null },
{ name: "Walmart Canada", bonus: "1% More" },
{ name: "PetSmart", bonus: "6% More" },
{ name: "Esso & Mobil", bonus: "1% More" },
];
export default function Page() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
src/app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<main className="min-h-screen bg-white">
<header className="w-full bg-white">
<div className="mx-auto flex max-w-6xl items-center justify-between px-4 py-4">
<div className="font-bold text-xl tracking-tight text-purple-700">
moola
</div>

<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
<nav className="hidden gap-6 text-sm text-slate-700 md:flex">
<a className="hover:text-slate-900" href="#">Shop</a>
<a className="hover:text-slate-900" href="#">How it Works</a>
<a className="hover:text-slate-900" href="#">Refer & Earn</a>
<a className="hover:text-slate-900" href="#">Merchant Solutions</a>
</nav>

<button className="rounded-full bg-purple-700 px-4 py-2 text-xs font-semibold text-white hover:bg-purple-800">
DOWNLOAD NOW
</button>
</div>
</header>
<section className="w-full bg-purple-700">
<div className="mx-auto grid max-w-6xl gap-4 px-4 py-8 md:grid-cols-2 md:py-10">
{/* main hero card */}
<div className="md:col-span-2 rounded-3xl bg-gradient-to-br from-purple-600 to-purple-900 p-8 text-white">
<p className="text-xs font-semibold tracking-widest opacity-90">
GIFTING MADE EASIER
</p>

<h1 className="mt-3 text-3xl font-extrabold leading-tight md:text-5xl">
Buy A Gift Card,
<span className="block">Get Cash Back Rewards</span>
</h1>

<p className="mt-4 max-w-xl text-sm opacity-90 md:text-base">
Shop gift cards from popular brands and earn rewards. Send instantly
through text or email.
</p>

<div className="mt-6 flex flex-wrap gap-3">
<button className="rounded-full bg-white px-5 py-2 text-sm font-semibold text-purple-800 hover:bg-slate-100">
Shop Gift Cards
</button>
<button className="rounded-full border border-white/40 px-5 py-2 text-sm font-semibold text-white hover:bg-white/10">
How it Works
</button>
</div>
</div>

<div className="rounded-3xl bg-white p-6">
<h2 className="text-lg font-bold text-slate-900">
Send gift cards instantly
<span className="block">through text.</span>
</h2>
<p className="mt-2 text-sm text-slate-600">
Fast gifting, no shipping stress.
</p>
<div className="mt-4 flex gap-2">
<div className="h-10 w-28 rounded-lg bg-slate-200" />
<div className="h-10 w-28 rounded-lg bg-slate-200" />
</div>
</div>
<div className="rounded-3xl bg-white p-6">
<h2 className="text-lg font-bold text-slate-900">
Invite a friend & get
<span className="block">cash back rewards.</span>
</h2>
<p className="mt-2 text-sm text-slate-600">
Simple referrals. Small wins add up.
</p>
</div>
</div>
</section>
<section className="mx-auto max-w-6xl px-4 py-10">
<div className="flex items-end justify-between gap-4">
<div>
<h2 className="text-2xl font-extrabold text-slate-900">
Popular Deals
</h2>
<p className="mt-1 text-sm text-slate-600">
A simplified grid inspired by Moola's marketplace cards.
</p>
</div>
<a className="text-sm font-semibold text-purple-700 hover:text-purple-900" href="#">
View all
</a>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org →
</a>
</footer>
</div>

<div className="mt-6 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{deals.map((d) => (
<div key={d.name} className="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">
<div className="flex items-center gap-3">
<div className="h-12 w-12 rounded-xl bg-slate-200" />
<div>
<h3 className="font-bold text-slate-900">{d.name}</h3>
<p className="text-xs text-slate-500">Gift Card</p>
</div>
</div>

<div className="mt-4">
{d.bonus ? (
<>
<p className="text-xs text-slate-600">Buy Now And Get</p>
<p className="mt-1 text-lg font-extrabold text-purple-700">
{d.bonus}
</p>
</>
) : (
<p className="text-sm text-slate-600">
Great everyday value and quick delivery.
</p>
)}
</div>

<button className="mt-5 w-full rounded-xl bg-purple-700 py-2 text-sm font-semibold text-white hover:bg-purple-800">
Buy Now
</button>
</div>
))}
</div>
</section>
</main>
);
}
Loading