Advertisement
Richbadniss

Main (page.tsx)

Dec 1st, 2024
16
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. 'use client'
  2.  
  3. import { Button } from "@/components/ui/button"
  4. import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
  5. import Footer from "@/components/ui/footer"
  6. import { Input } from "@/components/ui/input"
  7. import { useState } from "react"
  8. import { RoomVisualizer } from "@/components/ui/room-visualizer"
  9. import { TileComparison } from "@/components/ui/tile-comparison"
  10. import { ArrowRight, X } from "lucide-react"
  11.  
  12. interface TileOption {
  13.   name: string;
  14.   size: number;  // in inches
  15.   cost: number;  // per box
  16.   tilesPerBox: number;
  17.   durability: number;  // 1-10 rating
  18.   aestheticRating: number;  // 1-10 rating
  19.   laborCostPerSqFt: number;
  20.   maintenanceRating: number;  // 1-10 rating (10 being lowest maintenance)
  21. }
  22.  
  23. const DEFAULT_COST_PER_SQFT = 6.00
  24.  
  25. export default function Home() {
  26.   const [dimensions, setDimensions] = useState({
  27.     length: '',
  28.     width: '',
  29.     tileSize: '',
  30.     tileCost: '',
  31.     tilesPerBox: '',
  32.     laborCost: '',
  33.     materialCost: ''
  34.   })
  35.  
  36.   const [isMetric, setIsMetric] = useState(false)
  37.   const [result, setResult] = useState<{
  38.     totalArea: number;
  39.     tilesNeeded: number;
  40.     boxesNeeded: number;
  41.     totalCost: number;
  42.     materialCost: number;
  43.     laborCost: number;
  44.   } | null>(null)
  45.  
  46.   const [tileOptions, setTileOptions] = useState<TileOption[]>([
  47.     {
  48.       name: "Ceramic Tile",
  49.       size: 12, // 12 inches
  50.       cost: 35.99,
  51.       tilesPerBox: 8,
  52.       durability: 7,
  53.       aestheticRating: 8,
  54.       laborCostPerSqFt: 5.50,
  55.       maintenanceRating: 8
  56.     },
  57.     {
  58.       name: "Porcelain Tile",
  59.       size: 12,
  60.       cost: 8.99,
  61.       tilesPerBox: 10,
  62.       durability: 9,
  63.       aestheticRating: 9,
  64.       laborCostPerSqFt: 6.00,
  65.       maintenanceRating: 9
  66.     },
  67.     {
  68.       name: "Natural Stone",
  69.       size: 12,
  70.       cost: 12.99,
  71.       tilesPerBox: 12,
  72.       durability: 8,
  73.       aestheticRating: 10,
  74.       laborCostPerSqFt: 7.50,
  75.       maintenanceRating: 6
  76.     }
  77.   ]);
  78.  
  79.   const [showComparison, setShowComparison] = useState(false);
  80.  
  81.   const calculateTiles = () => {
  82.     // Convert inputs to numbers
  83.     let roomLength = parseFloat(dimensions.length)
  84.     let roomWidth = parseFloat(dimensions.width)
  85.     const tileSizeInches = parseFloat(dimensions.tileSize)
  86.     const costPerBox = parseFloat(dimensions.tileCost)
  87.     const tilesPerBox = parseFloat(dimensions.tilesPerBox)
  88.  
  89.     // Convert feet to meters if needed
  90.     if (!isMetric) {
  91.       roomLength = roomLength * 0.3048
  92.       roomWidth = roomWidth * 0.3048
  93.     }
  94.  
  95.     // Convert inches to centimeters for tile size
  96.     const tileSizeCm = tileSizeInches * 2.54
  97.  
  98.     // Validate inputs
  99.     if (!roomLength || !roomWidth || !tileSizeCm || !costPerBox || !tilesPerBox) {
  100.       alert('Please fill in all fields with valid numbers')
  101.       return
  102.     }
  103.  
  104.     // Convert room dimensions to cm² (from meters)
  105.     const roomArea = roomLength * roomWidth * 10000 // convert m² to cm²
  106.     const roomAreaSqFt = roomLength * roomWidth * 10.764 // convert m² to sq ft
  107.  
  108.     // Calculate tile size in cm²
  109.     const tileArea = tileSizeCm * tileSizeCm
  110.  
  111.     // Calculate number of boxes needed (adding 10% for waste)
  112.     const baseTilesNeeded = Math.ceil(roomArea / tileArea)
  113.     const tilesWithWaste = Math.ceil(baseTilesNeeded * 1.1)
  114.     const boxesNeeded = Math.ceil(tilesWithWaste / tilesPerBox)
  115.     const tileMaterialCost = boxesNeeded * costPerBox
  116.  
  117.     const laborCostPerSqFt = parseFloat(dimensions.laborCost) || DEFAULT_COST_PER_SQFT
  118.     const additionalMaterialCost = parseFloat(dimensions.materialCost) || 0
  119.  
  120.     // Calculate total costs
  121.     const laborCost = roomAreaSqFt * laborCostPerSqFt
  122.     const totalMaterialCost = tileMaterialCost + additionalMaterialCost
  123.     const totalCost = totalMaterialCost + laborCost
  124.  
  125.     setResult({
  126.       totalArea: roomArea / 10000, // convert back to m² for display
  127.       tilesNeeded: tilesWithWaste,
  128.       boxesNeeded: boxesNeeded,
  129.       totalCost: Number(totalCost.toFixed(2)),
  130.       materialCost: Number(totalMaterialCost.toFixed(2)),
  131.       laborCost: Number(laborCost.toFixed(2))
  132.     })
  133.  
  134.    
  135.     const updatedTileOptions = tileOptions.map(option => ({
  136.       ...option,
  137.       size: tileSizeInches,
  138.       laborCostPerSqFt: laborCostPerSqFt
  139.     }))
  140.    
  141.     setTileOptions(updatedTileOptions)
  142.   }
  143.  
  144.   return (
  145.     <main className="min-h-screen bg-white">
  146.       {/* Navigation */}
  147.       <nav className="py-3 px-3 sm:px-6 border-b">
  148.         <div className="max-w-6xl mx-auto flex flex-col sm:flex-row justify-between items-center gap-3 sm:gap-0">
  149.           <div className="flex items-center gap-2">
  150.             <div className="w-7 h-7 bg-blue-500 rounded-lg"></div>
  151.             <span className="font-semibold text-lg">TileCalc</span>
  152.           </div>
  153.           <div className="flex gap-3 sm:gap-6 text-sm">
  154.             <a href="#" className="text-gray-600 hover:text-gray-900">Home</a>
  155.             <a href="#" className="text-gray-600 hover:text-gray-900">Features</a>
  156.             <a href="#" className="text-gray-600 hover:text-gray-900">About</a>
  157.             <a href="#" className="text-gray-600 hover:text-gray-900">Support</a>
  158.           </div>
  159.           <Button className="bg-black text-white hover:bg-black/90 rounded-full transition-transform duration-300 hover:scale-105 px-3 sm:px-5">
  160.             Free Trial
  161.           </Button>
  162.         </div>
  163.       </nav>
  164.  
  165.       {/* Hero Section */}
  166.       <div className="max-w-6xl mx-auto px-3 sm:px-6 pt-8 sm:pt-16 pb-2 sm:pb-8 text-center">
  167.         <div className="inline-flex items-center gap-2 bg-gray-100 px-2.5 sm:px-3 py-1.5 rounded-full mb-5 sm:mb-6 text-sm">
  168.           <span className="text-blue-500">🎉 New</span>
  169.           <span className="text-gray-600">Advanced calculator</span>
  170.           <span className="text-gray-400"></span>
  171.         </div>
  172.        
  173.         <h1 className="text-3xl sm:text-[54px] font-bold leading-tight mb-3 sm:mb-5 bg-clip-text text-transparent bg-gradient-to-r from-black via-black/50 to-black animate-gradient bg-[length:200%_auto]">
  174.           Tile Calculator Pro
  175.         </h1>
  176.         <p className="text-base sm:text-lg text-gray-600 mb-6 sm:mb-10 max-w-xl mx-auto">
  177.           Calculate your tiling needs with precision. Save time and money by getting accurate measurements for your next project.
  178.         </p>
  179.       </div>
  180.  
  181.       <div className="flex flex-col sm:flex-row justify-center gap-2.5 sm:gap-3 px-3 sm:px-0 pb-12">
  182.         <Button className="bg-black text-white hover:bg-black/90 transition-transform duration-300 hover:scale-105 rounded-full px-5 sm:px-7 py-3 sm:py-5 text-base">
  183.           Start now - for free
  184.         </Button>
  185.         <Button variant="outline" className="rounded-full transition-transform duration-300 hover:scale-105 px-5 sm:px-7 py-3 sm:py-5 text-base">
  186.           Schedule a demo
  187.         </Button>
  188.       </div>
  189.  
  190.       {/* Calculator Section */}
  191.       <div className="max-w-6xl mx-auto px-3 sm:px-6">
  192.         <div className={`grid grid-cols-1 ${result ? 'sm:grid-cols-2' : 'sm:place-items-center'} gap-6`}>
  193.           <Card className={`bg-gray-50/50 border-0 shadow-lg ring-1 ring-black/5 hover:ring-blue-500/20 transition-all duration-300 hover:shadow-2xl hover:shadow-blue-500/10 rounded-2xl ${!result && 'max-w-lg w-full'}`}>
  194.             <CardHeader>
  195.               <CardTitle className="text-lg sm:text-xl">Calculate Your Project</CardTitle>
  196.             </CardHeader>
  197.             <CardContent className="space-y-3 sm:space-y-5">
  198.               <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
  199.                 <div>
  200.                   <div className="flex justify-between items-center mb-2">
  201.                     <label className="text-sm font-medium">Room Length ({isMetric ? 'm' : 'ft'})</label>
  202.                     <Button
  203.                       variant="outline"
  204.                       size="sm"
  205.                       onClick={() => setIsMetric(!isMetric)}
  206.                       className="text-xs h-6 px-2 rounded-full"
  207.                     >
  208.                       {isMetric ? 'Switch to ft' : 'Switch to m'}
  209.                     </Button>
  210.                   </div>
  211.                   <Input
  212.                     type="number"
  213.                     placeholder={`Enter length in ${isMetric ? 'meters' : 'feet'}`}
  214.                     value={dimensions.length}
  215.                     onChange={(e) => setDimensions(prev => ({...prev, length: e.target.value}))}
  216.                     className="bg-white border-gray-200 rounded-md"
  217.                   />
  218.                 </div>
  219.                 <div>
  220.                   <label className="text-sm font-medium mb-2 block">Room Width ({isMetric ? 'm' : 'ft'})</label>
  221.                   <Input
  222.                     type="number"
  223.                     placeholder={`Enter width in ${isMetric ? 'meters' : 'feet'}`}
  224.                     value={dimensions.width}
  225.                     onChange={(e) => setDimensions(prev => ({...prev, width: e.target.value}))}
  226.                     className="bg-white border-gray-200"
  227.                   />
  228.                 </div>
  229.               </div>
  230.  
  231.               <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
  232.                 <div>
  233.                   <label className="text-sm font-medium mb-2 block">Cost per Box ($)</label>
  234.                   <Input
  235.                     type="number"
  236.                     placeholder="Enter cost per box of tiles"
  237.                     value={dimensions.tileCost}
  238.                     onChange={(e) => setDimensions(prev => ({...prev, tileCost: e.target.value}))}
  239.                     className="bg-white border-gray-200"
  240.                   />
  241.                 </div>
  242.                 <div>
  243.                   <label className="text-sm font-medium mb-2 block">Tiles per Box</label>
  244.                   <Input
  245.                     type="number"
  246.                     placeholder="Enter number of tiles per box"
  247.                     value={dimensions.tilesPerBox}
  248.                     onChange={(e) => setDimensions(prev => ({...prev, tilesPerBox: e.target.value}))}
  249.                     className="bg-white border-gray-200"
  250.                   />
  251.                 </div>
  252.               </div>
  253.  
  254.               <div>
  255.                 <label className="text-sm font-medium mb-2 block">Labor Cost ($/sq ft)</label>
  256.                 <Input
  257.                   type="number"
  258.                   placeholder="Enter labor cost per sq ft"
  259.                   value={dimensions.laborCost}
  260.                   onChange={(e) => setDimensions(prev => ({...prev, laborCost: e.target.value}))}
  261.                   className="bg-white border-gray-200"
  262.                 />
  263.               </div>
  264.  
  265.               <div>
  266.                 <label className="text-sm font-medium mb-2 block">Additional Material Cost ($)</label>
  267.                 <Input
  268.                   type="number"
  269.                   placeholder="Enter additional materials cost (grout, spacers, etc.)"
  270.                   value={dimensions.materialCost}
  271.                   onChange={(e) => setDimensions(prev => ({...prev, materialCost: e.target.value}))}
  272.                   className="bg-white border-gray-200"
  273.                 />
  274.               </div>
  275.  
  276.               <div>
  277.                 <label className="text-sm font-medium mb-2 rounded-lg block">Tile Size (inches)</label>
  278.                 <Input
  279.                   type="number"
  280.                   placeholder="Enter tile size (e.g., 12 for 12x12in)"
  281.                   value={dimensions.tileSize}
  282.                   onChange={(e) => setDimensions(prev => ({...prev, tileSize: e.target.value}))}
  283.                   className="bg-white border-gray-200"
  284.                 />
  285.               </div>
  286.  
  287.               <Button
  288.                 className="w-full bg-black text-white hover:bg-black/90 transition-transform duration-300 hover:scale-105 rounded-full py-6 text-lg"
  289.                 onClick={calculateTiles}
  290.               >
  291.                 Calculate
  292.               </Button>
  293.  
  294.               {result && (
  295.                 <div className="bg-white rounded-2xl p-6 space-y-4">
  296.                   <div>
  297.                     <div className="text-sm text-gray-500 mb-1">Total Area</div>
  298.                     <div className="text-2xl font-semibold">{result.totalArea.toFixed(2)}</div>
  299.                   </div>
  300.                   <div>
  301.                     <div className="text-sm text-gray-500 mb-1">Boxes Required</div>
  302.                     <div className="text-2xl font-semibold">{result.boxesNeeded} boxes</div>
  303.                   </div>
  304.                   <div>
  305.                     <div className="text-sm text-gray-500 mb-1">Tiles Required</div>
  306.                     <div className="text-2xl font-semibold">{result.tilesNeeded} tiles</div>
  307.                   </div>
  308.                   <div>
  309.                     <div className="text-sm text-gray-500 mb-1">Total Cost</div>
  310.                     <div className="text-2xl font-semibold">
  311.                       ${result.totalCost.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  312.                     </div>
  313.                   </div>
  314.                   <div>
  315.                     <div className="text-sm text-gray-500 mb-1">Material Cost</div>
  316.                     <div className="text-2xl font-semibold">
  317.                       ${result.materialCost.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  318.                     </div>
  319.                   </div>
  320.                   <div>
  321.                     <div className="text-sm text-gray-500 mb-1">Labor Cost</div>
  322.                     <div className="text-2xl font-semibold">
  323.                       ${result.laborCost.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  324.                     </div>
  325.                   </div>
  326.                 </div>
  327.               )}
  328.  
  329.               {result && (
  330.                 <div className="mt-6">
  331.                   <Button
  332.                     onClick={() => setShowComparison(true)}
  333.                     className="group flex items-center gap-2 bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-full"
  334.                   >
  335.                     Compare Tile Options
  336.                     <ArrowRight className="w-4 h-4 transition-transform group-hover:translate-x-1" />
  337.                   </Button>
  338.                 </div>
  339.               )}
  340.             </CardContent>
  341.           </Card>
  342.  
  343.           {result && (
  344.             <div className="bg-gray-50/50 border-0 shadow-lg ring-1 ring-black/5 rounded-2xl  hover:ring-blue-500/20 transition-all duration-300 hover:shadow-2xl hover:shadow-blue-500/10 p-6">
  345.               <CardTitle className="text-lg sm:text-xl pb-3">Room Visualizer</CardTitle>
  346.               <RoomVisualizer
  347.                 roomLength={parseFloat(dimensions.length)}
  348.                 roomWidth={parseFloat(dimensions.width)}
  349.                 tileSize={parseFloat(dimensions.tileSize)}
  350.                 isMetric={isMetric}
  351.               />
  352.             </div>
  353.           )}
  354.         </div>
  355.       </div>
  356.  
  357.       {showComparison && (
  358.         <div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4">
  359.           <div className="bg-white rounded-2xl max-w-6xl w-full max-h-[90vh] overflow-y-auto p-6">
  360.             <div className="flex justify-between items-center mb-6">
  361.               <h2 className="text-2xl font-bold">Compare Tile Options</h2>
  362.               <Button
  363.                 variant="ghost"
  364.                 size="icon"
  365.                 onClick={() => setShowComparison(false)}
  366.               >
  367.                 <X className="h-5 w-5" />
  368.               </Button>
  369.             </div>
  370.            
  371.             <TileComparison
  372.               options={tileOptions}
  373.               roomArea={result?.totalArea || 0}
  374.               onSelectOption={(option) => {
  375.                 setDimensions(prev => ({
  376.                   ...prev,
  377.                   tileSize: option.size.toString(),
  378.                   tileCost: option.cost.toString()
  379.                 }));
  380.                 setShowComparison(false);
  381.               }}
  382.             />
  383.           </div>
  384.         </div>
  385.       )}
  386.  
  387.       <Footer />
  388.     </main>
  389.   )
  390. }
  391.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement