Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
1c1416d
Add deterministic CV pipeline for floor plan takeoff extraction
Arham-Tahir64 Feb 11, 2026
1d8f05c
Added git ignore
Arham-Tahir64 Feb 11, 2026
8aa61d7
Remove .env.local and backend/.env.example from tracking
Arham-Tahir64 Feb 11, 2026
8cd8705
Better detection on walls, not drawing ontop of measuring lines and o…
Arham-Tahir64 Feb 11, 2026
7b5f2dd
Dashboard updates
harrisj4 Feb 11, 2026
2083208
Display annotated pdf on frontend and accurate calculation
Arham-Tahir64 Feb 11, 2026
6a640cd
added annotated pdf to frontend with accurate with calculations
Arham-Tahir64 Feb 11, 2026
a4e129b
More accurate wall detection and increased thickness of annotation
Arham-Tahir64 Feb 11, 2026
07a41fb
Fix wall mask bleeding at junctions and gaps at corners
Arham-Tahir64 Feb 12, 2026
b9163e7
Redesign auth pages + add FloorPlanReveal hero animation
Arham-Tahir64 Feb 12, 2026
7bb5509
Add backend overview doc, update page and FloorPlanReveal
Arham-Tahir64 Feb 12, 2026
4f37866
CV takeoff fixes: crop controls, wall detection tuning, and overlay i…
Arham-Tahir64 Feb 27, 2026
df7da2d
feat: add annotation editor system and backend annotation APIs
Arham-Tahir64 Feb 27, 2026
d89d181
feat: remove text measurements from annotations and add overlay toggle
Arham-Tahir64 Feb 27, 2026
edae16c
Stabilize annotation editor and CV opening handling
Arham-Tahir64 Feb 27, 2026
8f6bcd4
Working on improving detection
Arham-Tahir64 Feb 28, 2026
fd5fe40
Front end dashboard refinement
Arham-Tahir64 Feb 28, 2026
42637e1
Improving estimation calculations
Arham-Tahir64 Mar 7, 2026
9009ed5
Draft vs Ready for takeoff
Arham-Tahir64 Mar 17, 2026
0f21e78
Add review rail fix-now actions
Arham-Tahir64 Mar 17, 2026
f14cc7d
Add issue-driven editor focus
Arham-Tahir64 Mar 17, 2026
9c41b79
Add on-canvas scale calibration
Arham-Tahir64 Mar 17, 2026
306317f
Add live snap feedback in editor
Arham-Tahir64 Mar 17, 2026
2b44a58
Prioritize builder actions in property panel
Arham-Tahir64 Mar 17, 2026
5b46ec7
Add multi-select wall QA workflows
Arham-Tahir64 Mar 17, 2026
7df291e
Fix bug with area being 0
Arham-Tahir64 Mar 18, 2026
53c98d4
Improve vector editing tool
Arham-Tahir64 Mar 19, 2026
dc5bcbe
Feature: Zoom in on rooms and select material
Arham-Tahir64 Mar 19, 2026
25bd00e
fix room detection
Arham-Tahir64 Mar 19, 2026
4588553
Frontend workflow fix for users
Arham-Tahir64 Mar 19, 2026
c0aaa07
Bug fixes
Arham-Tahir64 Mar 19, 2026
b216a15
Make room detection more robust
Arham-Tahir64 Mar 19, 2026
aaa690d
Improve wall detection for inner walls
Arham-Tahir64 Mar 19, 2026
353fc71
Bug fix
Arham-Tahir64 Mar 23, 2026
416a360
UI changes
Arham-Tahir64 Mar 23, 2026
00ae8ab
calculation engine complete
minhazzz Mar 30, 2026
bb76ec8
Merge pull request #11 from techstartucalgary/minhaz/features
Arham-Tahir64 Mar 30, 2026
e4799d0
Updated UI: removed clutter
Arham-Tahir64 Mar 24, 2026
2f057f9
Refining workflow
harrisj4 Apr 2, 2026
0cd88e4
Tweaks to the frontend
Apr 3, 2026
954839b
Finalize landing page hero and feature layout
harrisj4 Apr 24, 2026
bf1266e
Partially done dashboard. Needs a final polish
harrisj4 Apr 24, 2026
1c47a60
Polished dashboard. Pending additional features
harrisj4 Apr 24, 2026
9783756
Redesigning settings page with more user control. Pending features
harrisj4 Apr 24, 2026
741fd19
Minor tweak on landing page. Need to fix minor bugs, fix the settings…
harrisj4 Apr 24, 2026
aabd4d3
Fixed bugs and added changing password functionality in settings
harrisj4 Apr 25, 2026
0a89baf
Merge arham-CV into final backend
Arham-Tahir64 Apr 25, 2026
48b2a11
Redesign project editor/viewer
Arham-Tahir64 Apr 25, 2026
5a6a625
Remove old UI components
Arham-Tahir64 Apr 26, 2026
01f5335
Zoom in/out bug fix
Arham-Tahir64 Apr 26, 2026
181648f
bug fix
Arham-Tahir64 Apr 26, 2026
dff5866
Color coded rooms
Arham-Tahir64 Apr 28, 2026
da35b4a
Drop down menu associated to each individual asset
Arham-Tahir64 Apr 28, 2026
eca298d
Bug fix for rooms not showing fixed
Arham-Tahir64 Apr 28, 2026
b9463ad
Drop down menu shows edited sf, when adjusting room size
Arham-Tahir64 Apr 29, 2026
1d51d6c
Add button to show vectorized diagram
Arham-Tahir64 Apr 29, 2026
c7a2c45
Merge both faces of wall into 1
Arham-Tahir64 Apr 29, 2026
9bbe964
Add RFQ pages
harrisj4 Apr 29, 2026
a402e07
Minor tweaks and some data wiring
harrisj4 Apr 30, 2026
1ffa7d0
Fixing workflow
harrisj4 Apr 30, 2026
efa0e3f
initial commit
minhazzz May 1, 2026
e54447a
2nd
minhazzz May 2, 2026
7c81003
2
minhazzz May 2, 2026
d0f9951
update
minhazzz May 2, 2026
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
11 changes: 10 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# Copy this file to .env and fill in your values
# Copy this file to .env.local (frontend) and fill in your values

# Supabase (required for Login / Sign Up)
# Get these from your Supabase project: Settings → API
NEXT_PUBLIC_SUPABASE_URL=https://toeoclbhhrxnlhmyckcb.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRvZW9jbGJoaHJ4bmxobXlja2NiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjEyNTQ1MzEsImV4cCI6MjA3NjgzMDUzMX0.VUQ3-IOzmtoGgBJiPQBe9uA5V_hWQRz0DBSkpXdGCO4


# Backend API (for takeoff)
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000

# Database
# DATABASE_URL=postgresql://user:password@localhost:5432/dbname
Expand Down
2 changes: 0 additions & 2 deletions .env.local

This file was deleted.

11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ Thumbs.db
*.log
logs/

# Images
*.png
*.jpg
*.jpeg
*.gif
*.webp
*.ico
*.bmp
*.tiff
*.tif

# Misc
*.local

Expand Down
97 changes: 97 additions & 0 deletions app/auth/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import Link from 'next/link';
import Image from 'next/image';
import { ArrowLeft } from 'lucide-react';

/*
* ── Shared Auth Layout ───────────────────────────────────────────────
*
* Desktop : two-column (42 / 58 split) — brand panel + form panel
* Mobile : single column with compact dark header, then form
*
* The left panel re-uses the same gradient orbs + grid texture as the
* landing page so the brand feels continuous.
*
* Tweak points:
* - Column split → change lg:grid-cols-[42%_1fr]
* - Panel padding → p-10 xl:p-14
* - Right-panel bg → var(--auth-surface) in globals.css
* - Card max-width → max-w-[420px] on the wrapper below
*/
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen flex flex-col lg:grid lg:grid-cols-[42%_1fr]">
{/* ── Mobile header (< lg) ─────────────────────────────────── */}
<header className="lg:hidden flex items-center justify-between px-5 py-3.5 bg-[#030712] border-b border-white/5">
<Link
href="/"
className="flex items-center gap-1.5 text-sm text-white/60 hover:text-white transition-colors"
>
<ArrowLeft className="h-4 w-4" />
<span>Back</span>
</Link>

<Image
src="/images/FlowBuildr Primary Logo.png"
alt="FlowBuildr"
width={180}
height={40}
className="h-10 w-auto object-contain"
/>

{/* Spacer so logo stays centered */}
<div className="w-14" aria-hidden />
</header>

{/* ── Left brand panel (desktop) ───────────────────────────── */}
<div className="hidden lg:flex flex-col relative overflow-hidden bg-[#030712] selection:bg-blue-500/30">
{/* Decorative gradient orbs — same as landing page */}
<div className="absolute inset-0 pointer-events-none">
<div className="absolute top-16 left-8 w-[420px] h-[420px] bg-blue-600/10 rounded-full blur-[120px] opacity-50" />
<div className="absolute bottom-16 right-0 w-[500px] h-[500px] bg-indigo-600/10 rounded-full blur-[120px] opacity-50" />
{/* Grid texture — same as landing page */}
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.03)_1px,transparent_1px)] bg-[size:48px_48px]" />
</div>

<div className="relative z-10 flex flex-col h-full p-10 xl:p-14">
{/* Back link */}
<Link
href="/"
className="inline-flex items-center gap-2 text-sm text-white/50 hover:text-white transition-colors w-fit"
>
<ArrowLeft className="h-4 w-4" />
Back to home
</Link>



{/* Headline — vertically centred in remaining space */}
<div className="mt-auto mb-auto">
<h1 className="text-3xl xl:text-[2.5rem] font-bold text-white leading-snug tracking-tight">
Precision takeoffs,
<br />
powered by AI.
</h1>
<p className="mt-5 text-[15px] text-white/45 leading-relaxed max-w-[340px]">
Upload your blueprints, get accurate material quantities,
and streamline your construction workflow.
</p>
</div>

{/* Bottom tag */}
<p className="text-[11px] text-white/25 tracking-[0.15em] uppercase">
AI Integrated Takeoff for Builders
</p>
</div>
</div>

{/* ── Right auth panel ─────────────────────────────────────── */}
<div className="flex-1 flex items-center justify-center bg-[var(--auth-surface)] px-5 py-10 sm:px-8 lg:px-12 min-h-[calc(100vh-52px)] lg:min-h-screen">
<div className="w-full max-w-[420px]">{children}</div>
</div>
</div>
);
}
201 changes: 201 additions & 0 deletions app/auth/reset-password/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
'use client';

import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { Lock, Eye, EyeOff, ArrowRight } from 'lucide-react';
import { supabase } from '@/lib/supabase';

export default function ResetPasswordPage() {
const router = useRouter();
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const [status, setStatus] = useState<'checking' | 'ready' | 'invalid'>('checking');
const [localError, setLocalError] = useState('');
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
let mounted = true;

const checkRecoverySession = async () => {
const { data } = await supabase.auth.getSession();
if (!mounted) return;

if (data.session) {
setStatus('ready');
return;
}

// Supabase can take a brief moment to parse hash tokens.
setTimeout(async () => {
const { data: retryData } = await supabase.auth.getSession();
if (!mounted) return;
setStatus(retryData.session ? 'ready' : 'invalid');
}, 400);
};

const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
if (!mounted) return;
if (event === 'PASSWORD_RECOVERY' || session) {
setStatus('ready');
}
});

checkRecoverySession();

return () => {
mounted = false;
authListener.subscription.unsubscribe();
};
}, []);

const handleUpdatePassword = async (e: React.FormEvent) => {
e.preventDefault();
setLocalError('');

if (password !== confirmPassword) {
setLocalError('Passwords do not match.');
return;
}

if (password.length < 6) {
setLocalError('Password must be at least 6 characters long.');
return;
}

setIsLoading(true);
try {
const { error } = await supabase.auth.updateUser({ password });
if (error) throw error;
router.push('/auth/signin?reset=success');
} catch (error: any) {
setLocalError(error?.message || 'Failed to update password.');
} finally {
setIsLoading(false);
}
};

const inputBase =
'w-full h-12 rounded-xl border border-[var(--auth-border)] bg-white text-sm text-gray-900 ' +
'placeholder:text-gray-400 focus:outline-none focus:border-[#0099FC] focus:ring-2 focus:ring-[var(--auth-ring)] ' +
'transition-all duration-150';

return (
<>
<div className="mb-8">
<h2 className="text-[1.65rem] font-bold text-gray-900 tracking-tight leading-tight">
Reset password
</h2>
<p className="mt-1.5 text-sm text-gray-500">
Choose a new password for your account
</p>
</div>

<div className="bg-white rounded-2xl border border-[var(--auth-border)] shadow-sm p-7 sm:p-8">
{status === 'checking' && (
<div className="p-3 rounded-xl bg-blue-50 border border-blue-100 text-blue-700 text-sm text-center">
Verifying your reset link...
</div>
)}

{status === 'invalid' && (
<div className="space-y-4">
<div className="p-3 rounded-xl bg-red-50 border border-red-100 text-red-700 text-sm text-center">
This reset link is invalid or expired. Please request a new one.
</div>
<Link
href="/auth/signin"
className="block text-center w-full h-11 leading-[44px] rounded-xl font-semibold text-sm text-white bg-[#0099FC] hover:bg-[#0088e0] transition-colors"
>
Back to Sign In
</Link>
</div>
)}

{status === 'ready' && (
<form onSubmit={handleUpdatePassword} className="space-y-5">
{localError && (
<div className="p-3 rounded-xl bg-red-50 border border-red-100 text-red-600 text-sm text-center">
{localError}
</div>
)}

<div>
<label htmlFor="new-password" className="block text-sm font-medium text-gray-700 mb-1.5">
New password
</label>
<div className="relative">
<Lock className="absolute left-3.5 top-1/2 -translate-y-1/2 h-[18px] w-[18px] text-gray-400 pointer-events-none" />
<input
id="new-password"
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Min. 6 characters"
className={`${inputBase} pl-11 pr-12`}
required
/>
<button
type="button"
onClick={() => setShowPassword((v) => !v)}
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
aria-label={showPassword ? 'Hide password' : 'Show password'}
>
{showPassword ? <EyeOff className="h-[18px] w-[18px]" /> : <Eye className="h-[18px] w-[18px]" />}
</button>
</div>
</div>

<div>
<label htmlFor="confirm-new-password" className="block text-sm font-medium text-gray-700 mb-1.5">
Confirm new password
</label>
<div className="relative">
<Lock className="absolute left-3.5 top-1/2 -translate-y-1/2 h-[18px] w-[18px] text-gray-400 pointer-events-none" />
<input
id="confirm-new-password"
type={showConfirm ? 'text' : 'password'}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Re-enter your password"
className={`${inputBase} pl-11 pr-12`}
required
/>
<button
type="button"
onClick={() => setShowConfirm((v) => !v)}
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
aria-label={showConfirm ? 'Hide password' : 'Show password'}
>
{showConfirm ? <EyeOff className="h-[18px] w-[18px]" /> : <Eye className="h-[18px] w-[18px]" />}
</button>
</div>
</div>

<button
type="submit"
disabled={isLoading}
className="w-full h-12 rounded-xl font-semibold text-sm text-white
bg-gradient-to-r from-blue-600 to-indigo-600
hover:from-blue-500 hover:to-indigo-500
shadow-lg shadow-blue-600/20 hover:shadow-blue-500/30
disabled:opacity-60 disabled:cursor-not-allowed
active:scale-[0.98]
transition-all duration-150
flex items-center justify-center gap-2"
>
{isLoading ? 'Updating password...' : (
<>
Update Password
<ArrowRight className="h-4 w-4" />
</>
)}
</button>
</form>
)}
</div>
</>
);
}
Loading