├── .gitattributes ├── prisma ├── dev.db └── schema.prisma ├── public ├── Hero.JPG ├── Loan.jpg ├── Loan.png ├── hero.png ├── logo.jpg ├── logo.png ├── Hero2.jpg ├── Loan2.jpg ├── Rates.png ├── hero2.png ├── hero21.png ├── logo2.jpg ├── logo2.png ├── calculate.jpg └── calculator.png ├── src ├── app │ ├── favicon.ico │ ├── fonts │ │ ├── GeistVF.woff │ │ └── GeistMonoVF.woff │ ├── sign-up │ │ └── [[...sign-up]] │ │ │ └── page.tsx │ ├── apply │ │ └── page.tsx │ ├── sign-in │ │ └── [[...sign-in]] │ │ │ └── page.tsx │ ├── apply4 │ │ ├── page.tsx │ │ └── action.ts │ ├── admin │ │ └── page.tsx │ ├── start │ │ ├── page.tsx │ │ └── theme.ts │ ├── page.tsx │ ├── success │ │ └── page.tsx │ ├── blog │ │ └── page.tsx │ ├── blogprisma │ │ ├── page.tsx │ │ └── Postcardprisma.tsx │ ├── layout.tsx │ ├── globals.css │ ├── contact │ │ └── page.tsx │ └── affordability │ │ └── page.tsx ├── data │ ├── data.ts │ └── json.ts ├── lib │ ├── sleep.ts │ ├── numberWithCommas.ts │ ├── utils.ts │ ├── db.ts │ ├── shimmer.ts │ ├── connectToDb.tsx │ └── action.ts ├── components │ ├── calculator │ │ └── page.tsx │ ├── Post │ │ ├── PostTest.tsx │ │ └── PostCard.tsx │ ├── Footer │ │ └── Map.tsx │ ├── Navbar │ │ ├── User.tsx │ │ ├── ApplyBtn.tsx │ │ ├── Phone.tsx │ │ ├── Profile.tsx │ │ └── DropdownBtn.tsx │ ├── container │ │ └── Container.tsx │ ├── ThemeProvider.tsx │ ├── Container.tsx │ ├── Dashboard │ │ ├── SubmitButton.tsx │ │ ├── NewPost.tsx │ │ ├── NewPostPrisma.tsx │ │ ├── Dashboard.tsx │ │ ├── DashboardCards.tsx │ │ ├── NewPost2.tsx │ │ └── Dashboard2.tsx │ ├── ApplicationForm │ │ ├── ApplicationForm.tsx │ │ ├── json.ts │ │ └── theme.ts │ ├── ThemeToggler │ │ └── ThemeToggler.tsx │ ├── ui │ │ ├── label.tsx │ │ ├── textarea.tsx │ │ ├── input.tsx │ │ ├── progress.tsx │ │ ├── toaster.tsx │ │ ├── checkbox.tsx │ │ ├── slider.tsx │ │ ├── switch.tsx │ │ ├── avatar.tsx │ │ ├── radio-group.tsx │ │ ├── button.tsx │ │ ├── tabs.tsx │ │ ├── card.tsx │ │ ├── accordion.tsx │ │ ├── table.tsx │ │ ├── dialog.tsx │ │ ├── form.tsx │ │ ├── toast.tsx │ │ ├── navigation-menu.tsx │ │ ├── select.tsx │ │ └── dropdown-menu.tsx │ ├── Loan │ │ └── Loan.tsx │ ├── Hero │ │ └── Hero.tsx │ ├── SupportModal │ │ └── SupportModal.tsx │ ├── Faq │ │ └── Faq.tsx │ ├── Navbar.tsx │ ├── Rates │ │ ├── Rates.tsx │ │ └── RatesCalculator.tsx │ └── NavbarLinks.tsx ├── middleware.ts ├── models │ ├── PostModel.ts │ └── ApplyModel.ts └── hooks │ └── use-toast.ts ├── postcss.config.mjs ├── next.config.mjs ├── .env ├── components.json ├── .gitignore ├── tsconfig.json ├── LICENSE ├── package.json ├── tailwind.config.ts └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /prisma/dev.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/prisma/dev.db -------------------------------------------------------------------------------- /public/Hero.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/Hero.JPG -------------------------------------------------------------------------------- /public/Loan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/Loan.jpg -------------------------------------------------------------------------------- /public/Loan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/Loan.png -------------------------------------------------------------------------------- /public/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/hero.png -------------------------------------------------------------------------------- /public/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/logo.jpg -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/Hero2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/Hero2.jpg -------------------------------------------------------------------------------- /public/Loan2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/Loan2.jpg -------------------------------------------------------------------------------- /public/Rates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/Rates.png -------------------------------------------------------------------------------- /public/hero2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/hero2.png -------------------------------------------------------------------------------- /public/hero21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/hero21.png -------------------------------------------------------------------------------- /public/logo2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/logo2.jpg -------------------------------------------------------------------------------- /public/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/logo2.png -------------------------------------------------------------------------------- /public/calculate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/calculate.jpg -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /public/calculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/public/calculator.png -------------------------------------------------------------------------------- /src/data/data.ts: -------------------------------------------------------------------------------- 1 | export const navlinks = [ 2 | { 3 | title: 1, 4 | url: "#", 5 | }, 6 | ]; -------------------------------------------------------------------------------- /src/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/src/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /src/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiladJoodi/Mortgages_Hamed/HEAD/src/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /src/lib/sleep.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); 2 | // await sleep(3000); 3 | -------------------------------------------------------------------------------- /src/lib/numberWithCommas.ts: -------------------------------------------------------------------------------- 1 | export function numberWithCommas(x:number){ 2 | return Number(x.toFixed(3)).toLocaleString(); 3 | } 4 | 5 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/components/calculator/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | const Calculator = () => { 3 | return ( 4 |
5 | Enter 6 |
7 | ); 8 | } 9 | 10 | export default Calculator; -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/app/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs"; 2 | 3 | export default function Page() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } -------------------------------------------------------------------------------- /src/components/Post/PostTest.tsx: -------------------------------------------------------------------------------- 1 | 2 | const PostTest = ({title}:any) => { 3 | console.log(title) 4 | return ( 5 |
6 | Enter 7 |
8 | ); 9 | } 10 | 11 | export default PostTest; -------------------------------------------------------------------------------- /src/components/Footer/Map.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | 4 | const Map = () => { 5 | 6 | const position = [51.505, -0.09] 7 | 8 | return ( 9 |
10 | map 11 |
12 | ); 13 | } 14 | 15 | export default Map; -------------------------------------------------------------------------------- /src/components/Navbar/User.tsx: -------------------------------------------------------------------------------- 1 | // ⚠️ Developing 2 | import { currentUser } from '@clerk/nextjs/server' 3 | 4 | export default async function User() { 5 | 6 | const user = await currentUser() 7 | 8 | 9 | return
Hello {user?.firstName}!
10 | } -------------------------------------------------------------------------------- /src/app/apply/page.tsx: -------------------------------------------------------------------------------- 1 | import ApplicationForm from "@/components/ApplicationForm/ApplicationForm"; 2 | 3 | const Apply = () => { 4 | return ( 5 |
6 | 7 | 8 |
9 | ); 10 | } 11 | 12 | export default Apply; -------------------------------------------------------------------------------- /src/app/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs"; 2 | 3 | export default function Page() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | protocol: "https", 7 | hostname: "**", 8 | }, 9 | ], 10 | } 11 | }; 12 | 13 | export default nextConfig; 14 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Database for Mongoose 2 | MONGODB_URI=mongodb+srv://winbetacom:winbetacom@cluster0.lq7ohfu.mongodb.net/mortgage-hamed?retryWrites=true&w=majority&appName=Cluster0 3 | 4 | # Database for Prisma 5 | DATABASE_URL="mongodb+srv://winbetacom:winbetacom@cluster0.lq7ohfu.mongodb.net/mortgage-hamed?retryWrites=true&w=majority&appName=Cluster0" -------------------------------------------------------------------------------- /src/components/container/Container.tsx: -------------------------------------------------------------------------------- 1 | 2 | interface IContainer { 3 | children: React.ReactNode 4 | } 5 | 6 | const Container = ({children} : IContainer) => { 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | 14 | export default Container; -------------------------------------------------------------------------------- /src/app/apply4/page.tsx: -------------------------------------------------------------------------------- 1 | import MortgageLoanForm from '@/components/apply4/MortgageLoanForm'; 2 | import { Toaster } from 'react-hot-toast' 3 | 4 | const page = () => { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | }; 12 | 13 | export default page; 14 | -------------------------------------------------------------------------------- /src/components/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ThemeProvider as NextThemesProvider } from "next-themes" 5 | import { type ThemeProviderProps } from "next-themes/dist/types" 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children} 9 | } -------------------------------------------------------------------------------- /src/components/Container.tsx: -------------------------------------------------------------------------------- 1 | 2 | interface IContainer { 3 | children: React.ReactNode 4 | } 5 | 6 | const Container = ({children} : IContainer) => { 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | 14 | export default Container; -------------------------------------------------------------------------------- /src/app/admin/page.tsx: -------------------------------------------------------------------------------- 1 | import Container from "@/components/Container"; 2 | import Dashboard from "@/components/Dashboard/Dashboard"; 3 | import Dashboard2 from "@/components/Dashboard/Dashboard2"; 4 | 5 | const page = () => { 6 | return ( 7 |
8 | 9 | 10 | 11 |
12 | ); 13 | }; 14 | 15 | export default page; 16 | -------------------------------------------------------------------------------- /src/lib/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | 3 | const prismaClientSingleton = () => { 4 | return new PrismaClient() 5 | } 6 | 7 | declare const globalThis: { 8 | prismaGlobal: ReturnType; 9 | } & typeof global; 10 | 11 | const prisma = globalThis.prismaGlobal ?? prismaClientSingleton() 12 | 13 | export default prisma 14 | 15 | if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma 16 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /src/components/Dashboard/SubmitButton.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { Button } from "@/components/ui/button"; 3 | import React from "react"; 4 | import { useFormStatus } from "react-dom"; 5 | 6 | type TitleProps = { 7 | title: string 8 | } 9 | 10 | function SubmitButton({title}:TitleProps) { 11 | 12 | const {pending} = useFormStatus(); 13 | 14 | return ( 15 | 18 | ) 19 | } 20 | 21 | 22 | export default SubmitButton; 23 | -------------------------------------------------------------------------------- /src/components/Navbar/ApplyBtn.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Button } from "../ui/button"; 3 | 4 | const ApplyBtn = () => { 5 | return ( 6 | 7 | 10 | 11 | ); 12 | }; 13 | 14 | export default ApplyBtn; 15 | -------------------------------------------------------------------------------- /src/app/start/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import "survey-core/defaultV2.min.css"; 3 | import { Model } from "survey-core"; 4 | import { Survey } from "survey-react-ui"; 5 | import Container from "@/components/Container"; 6 | 7 | import { json } from "./json"; 8 | 9 | const Start = () => { 10 | const model = new Model(json); 11 | return ( 12 |
13 | 14 |
15 | 16 |
17 |
18 |
19 | ); 20 | }; 21 | 22 | export default Start; 23 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server' 2 | 3 | const isProtectedRoute = createRouteMatcher(['/apply','/start']) 4 | 5 | export default clerkMiddleware((auth, req) => { 6 | if (isProtectedRoute(req)) auth().protect() 7 | }) 8 | 9 | export const config = { 10 | matcher: [ 11 | // Skip Next.js internals and all static files, unless found in search params 12 | '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', 13 | // Always run for API routes 14 | '/(api|trpc)(.*)', 15 | ], 16 | } -------------------------------------------------------------------------------- /src/components/ApplicationForm/ApplicationForm.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import "survey-core/defaultV2.min.css"; 3 | import { Model } from "survey-core"; 4 | import { Survey } from "survey-react-ui"; 5 | import Container from "../Container"; 6 | 7 | import { json } from "./json"; 8 | 9 | const ApplicationForm = () => { 10 | const model = new Model(json); 11 | return ( 12 |
13 | 14 |
15 | 16 |
17 |
18 |
19 | ); 20 | }; 21 | 22 | export default ApplicationForm; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /src/models/PostModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const PostSchema = new mongoose.Schema( 4 | { 5 | title: { 6 | type: String, 7 | default: "No Title", 8 | min: 2, 9 | max: 100, 10 | // required: true 11 | }, 12 | content: { 13 | type: String, 14 | default: "No content", 15 | min: 2, 16 | max: 100, 17 | }, 18 | imageUrl: { 19 | type: String, 20 | default: "No content", 21 | min: 2, 22 | max: 100, 23 | }, 24 | }, 25 | { timestamps: true } 26 | ); 27 | 28 | const Post = mongoose.models.Post || mongoose.model("Post", PostSchema); 29 | export default Post; 30 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import ApplicationForm from "@/components/ApplicationForm/ApplicationForm"; 2 | import Container from "@/components/Container"; 3 | import { Faq } from "@/components/Faq/Faq"; 4 | import Map from "@/components/Footer/Map"; 5 | import Hero from "@/components/Hero/Hero"; 6 | import Loan from "@/components/Loan/Loan"; 7 | import Rates from "@/components/Rates/Rates"; 8 | import Testimonial from "@/components/Testimonial/Testimonial"; 9 | 10 | export default function Home() { 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 |
19 | ); 20 | } 21 | // https://illuminate.marketingmortgagewebsites.com/ 22 | -------------------------------------------------------------------------------- /src/components/ThemeToggler/ThemeToggler.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { MoonIcon } from "@radix-ui/react-icons"; 4 | import { useTheme } from "next-themes"; 5 | import { Button } from "../ui/button"; 6 | import { Sun } from "lucide-react"; 7 | 8 | const ThemeToggler = () => { 9 | 10 | const {theme, setTheme} = useTheme(); 11 | 12 | return ( 13 |
14 | 18 |
19 | ); 20 | }; 21 | 22 | export default ThemeToggler; 23 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 | 129 | 130 | 131 |
132 | 138 |
139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | ); 147 | }; 148 | 149 | export default page; 150 | -------------------------------------------------------------------------------- /src/components/Dashboard/Dashboard2.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { Input } from "@/components/ui/input"; 5 | import { 6 | Home, 7 | SquarePen, 8 | CircleCheckBig, 9 | Sparkles, 10 | Rss, 11 | Power, 12 | Headset, 13 | Search, 14 | } from "lucide-react"; 15 | import DashboardCards from "./DashboardCards"; 16 | import { useState } from "react"; 17 | import { NewPost2 } from "./NewPost2"; 18 | import Link from "next/link"; 19 | import { SupportModal } from "../SupportModal/SupportModal"; 20 | import { useClerk } from "@clerk/nextjs"; 21 | import NewPostPrisma from "./NewPostPrisma"; 22 | 23 | export default function Dashboard2() { 24 | 25 | const [userSelect, setUserSelect] = useState(); 26 | const { signOut } = useClerk(); 27 | 28 | return ( 29 |
30 | {/* Sidebar */} 31 | 136 | 137 | {/* Main Content */} 138 |
139 | {/* Header */} 140 |
141 |
142 |

143 | Dashboard 144 |

145 |
146 | 151 | 155 |
156 |
157 |
158 | 159 | {/* Dashboard Content */} 160 |
{userSelect}
161 |
162 | 163 |
164 |
165 |
166 | ); 167 | } 168 | -------------------------------------------------------------------------------- /src/components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { 5 | CaretSortIcon, 6 | CheckIcon, 7 | ChevronDownIcon, 8 | ChevronUpIcon, 9 | } from "@radix-ui/react-icons" 10 | import * as SelectPrimitive from "@radix-ui/react-select" 11 | 12 | import { cn } from "@/lib/utils" 13 | 14 | const Select = SelectPrimitive.Root 15 | 16 | const SelectGroup = SelectPrimitive.Group 17 | 18 | const SelectValue = SelectPrimitive.Value 19 | 20 | const SelectTrigger = React.forwardRef< 21 | React.ElementRef, 22 | React.ComponentPropsWithoutRef 23 | >(({ className, children, ...props }, ref) => ( 24 | span]:line-clamp-1", 28 | className 29 | )} 30 | {...props} 31 | > 32 | {children} 33 | 34 | 35 | 36 | 37 | )) 38 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 39 | 40 | const SelectScrollUpButton = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 | 52 | 53 | 54 | )) 55 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 56 | 57 | const SelectScrollDownButton = React.forwardRef< 58 | React.ElementRef, 59 | React.ComponentPropsWithoutRef 60 | >(({ className, ...props }, ref) => ( 61 | 69 | 70 | 71 | )) 72 | SelectScrollDownButton.displayName = 73 | SelectPrimitive.ScrollDownButton.displayName 74 | 75 | const SelectContent = React.forwardRef< 76 | React.ElementRef, 77 | React.ComponentPropsWithoutRef 78 | >(({ className, children, position = "popper", ...props }, ref) => ( 79 | 80 | 91 | 92 | 99 | {children} 100 | 101 | 102 | 103 | 104 | )) 105 | SelectContent.displayName = SelectPrimitive.Content.displayName 106 | 107 | const SelectLabel = React.forwardRef< 108 | React.ElementRef, 109 | React.ComponentPropsWithoutRef 110 | >(({ className, ...props }, ref) => ( 111 | 116 | )) 117 | SelectLabel.displayName = SelectPrimitive.Label.displayName 118 | 119 | const SelectItem = React.forwardRef< 120 | React.ElementRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )) 139 | SelectItem.displayName = SelectPrimitive.Item.displayName 140 | 141 | const SelectSeparator = React.forwardRef< 142 | React.ElementRef, 143 | React.ComponentPropsWithoutRef 144 | >(({ className, ...props }, ref) => ( 145 | 150 | )) 151 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 152 | 153 | export { 154 | Select, 155 | SelectGroup, 156 | SelectValue, 157 | SelectTrigger, 158 | SelectContent, 159 | SelectLabel, 160 | SelectItem, 161 | SelectSeparator, 162 | SelectScrollUpButton, 163 | SelectScrollDownButton, 164 | } 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wakatime 2 | 3 | # Full Stack Mortgage Website 4 | 5 | 6 | A FullStack web application suitable for Mortgage use. 7 | In this project, an admin dashboard has also been developed, meaning you can publish new content. 8 | Authentication in this project is handled using Clerk. 9 | 10 | For communication with the MongoDB server, I used the mongoose library. 11 | Additionally, the posts you publish are available at the /blog address, where the information is updated using revalidationPath after your post is registered. 12 | I used Shadcn components. 13 | 14 | Dark/Light mode has been developed in this project. 15 | On the /apply page, there is a multi-step form developed using Surveyjs.io. This was my first time using Surveyjs in this project, and it was amazing! I highly recommend you try it 🤗. 16 | It's easy to use. If you need guidance, feel free to check out my post on LinkedIn: 17 | 18 | https://www.linkedin.com/feed/update/urn:li:activity:7240008028232572929/ 19 | 20 | Thank you for your attention. 21 | This project is available at the following address: 22 | [**Demo** ✔️](https://mortgages-hamed.vercel.app/).\ 23 | Thank you sincerely. 🙏 24 | 25 | On this admin dashboard page, I've developed two forms—one that submits data using Mongoose and another using Prisma. The form data is validated with React Hook Form and Zod. 26 | https://mortgages-hamed.vercel.app/admin 27 | 28 | On this page, the information is fetched and displayed using Prisma. 29 | https://mortgages-hamed.vercel.app/blogprisma 30 | 31 | On this page, the information is fetched and displayed using Mongoose. 32 | https://mortgages-hamed.vercel.app/blog 33 | 34 | On the homepage, I've added a minimal Calculate EMI feature. 35 | https://mortgages-hamed.vercel.app/ 36 | 37 | This form is registered in the MongoDB database using Prisma. 38 | https://mortgages-hamed.vercel.app/apply4 39 | 40 | This form is registered using Mongoose. 41 | https://mortgages-hamed.vercel.app/apply2 42 | 43 | This is an advanced Mortgage Affordability Calculator that can calculate very quickly. 44 | https://mortgages-hamed.vercel.app/affordability 45 | 46 | In this Mortgage EMI Calculator, I've tried to compute user inputs in real-time and display the results along with a chart. 47 | https://mortgages-hamed.vercel.app/calculator 48 | 49 | 50 | 51 | Next 57 | 58 | 59 | Next 65 | 66 | 67 | 72 | 73 | 78 | 79 | 80 | 85 | 86 | 87 | 92 | 93 | 94 | 99 | 100 | 101 | 106 | 107 | 108 | 113 | 114 | 115 | 120 | 121 |
122 | 123 | 124 | ![](https://s32.picofile.com/file/8479043242/hamed.png) 125 | ![](https://s32.picofile.com/file/8479043250/hamed2.png) 126 | ![](https://s32.picofile.com/file/8479043268/hamed03.png) 127 | ![](https://s32.picofile.com/file/8479043276/hamed04.PNG) 128 | ![](https://s32.picofile.com/file/8479043284/hamed05.PNG) 129 | ![](https://s32.picofile.com/file/8479043292/hamed06.PNG) 130 | ![](https://s32.picofile.com/file/8479043300/hamed07.PNG) 131 | ![](https://s32.picofile.com/file/8479043318/hamed08.PNG) 132 | ![](https://s32.picofile.com/file/8479043326/hamed09.PNG) 133 | ![](https://s32.picofile.com/file/8479043334/hamed10.PNG) 134 | 135 | Full: 136 | ![](https://s32.picofile.com/file/8479043368/hamed_full.png) 137 | 138 | 139 |

Connect with me:

140 | 141 |

142 | miladjoodi 150 | 152 | https://www.linkedin.com/in/miladjoodi/ 162 | Milad's Linkedin 172 |

173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/lib/action.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import Post from "@/models/PostModel"; 4 | import dbConnect from "./connectToDb"; 5 | import { revalidatePath } from "next/cache"; 6 | import { z } from "zod"; 7 | import Apply from "@/models/ApplyModel"; 8 | import prisma from "./db"; 9 | 10 | // export interface FormDataType { 11 | // title: string; 12 | // content: string; 13 | // imageUrl: string; 14 | // } 15 | 16 | // POST Method 17 | export const addPost = async (formData: any) => { 18 | try { 19 | await dbConnect(); 20 | const data = { 21 | title: formData.title, 22 | content: formData.content, 23 | imageUrl: formData.imageUrl, 24 | }; 25 | const saveUser = await new Post(data).save(); 26 | revalidatePath("/blog"); 27 | console.log(saveUser); 28 | } catch (error) { 29 | console.log(error); 30 | } 31 | }; 32 | 33 | // GET Method 34 | export const getPosts = async () => { 35 | try { 36 | await dbConnect(); 37 | const response = await Post.find().exec(); 38 | console.log(response); 39 | return response; 40 | } catch (error) { 41 | console.log(error); 42 | } 43 | }; 44 | 45 | // POST APPLY 2 46 | // zod Schema Validation 47 | const applyFormSchema = z.object({ 48 | fullName: z.string(), 49 | email: z.string().email(), 50 | phoneNumber: z 51 | .string() 52 | .min(10) 53 | .refine((val) => /^\d+$/.test(val), { 54 | message: "Phone number must contain only digits", 55 | }), 56 | loanAmount: z.number().min(1000).max(1000000), 57 | loanPurpose: z.string(), 58 | employmentStatus: z.string(), 59 | annualIncome: z.number(), 60 | creditScore: z.number().min(300).max(800), 61 | hasCollateral: z.boolean(), 62 | agreeToTerms: z.boolean(), 63 | }); 64 | 65 | export const submitApply = async (data: z.infer) => { 66 | console.log("pre try"); 67 | try { 68 | await dbConnect(); 69 | console.log("Connected to database"); 70 | 71 | const validatedData = applyFormSchema.parse(data); 72 | 73 | // const applicationData = { 74 | // ...validatedData 75 | // } 76 | const saveForm = await new Apply(validatedData).save(); 77 | // revalidatePath('/blog') 78 | console.log("Application saved:", saveForm); 79 | return { success: true, message: "Application submitted successfully." }; 80 | } catch (error) { 81 | console.error("Error submitting loan application:", error); 82 | return { 83 | success: false, 84 | message: 85 | "There was an error submitting your application. Please try again.", 86 | }; 87 | } 88 | }; 89 | 90 | // POST APPLY 3 91 | const applicationSchema = z.object({ 92 | personalInfo: z.object({ 93 | firstName: z.string(), 94 | lastName: z.string(), 95 | email: z.string().email(), 96 | phone: z.string(), 97 | dob: z.string(), 98 | }), 99 | loanInfo: z.object({ 100 | loanType: z.enum(["mortgage", "personal", "auto"]), 101 | loanAmount: z.number(), 102 | loanTerm: z.number(), 103 | interestRate: z.number(), 104 | }), 105 | employmentInfo: z.object({ 106 | employmentStatus: z.enum(["employed", "self-employed", "unemployed"]), 107 | employerName: z.string().optional(), 108 | annualIncome: z.number(), 109 | yearsAtJob: z.number(), 110 | }), 111 | assets: z.array( 112 | z.object({ 113 | assetType: z.enum(["savings", "investments", "property"]), 114 | value: z.number(), 115 | }) 116 | ), 117 | liabilities: z.array( 118 | z.object({ 119 | liabilityType: z.enum(["credit_card", "student_loan", "car_loan"]), 120 | amount: z.number(), 121 | }) 122 | ), 123 | documents: z.object({ 124 | idProof: z.any().optional(), 125 | incomeProof: z.any().optional(), 126 | bankStatements: z.any().optional(), 127 | }), 128 | termsAccepted: z.boolean(), 129 | }); 130 | 131 | export async function submitApply3(data: z.infer) { 132 | try { 133 | // Validate the data 134 | const validatedData = applicationSchema.parse(data); 135 | 136 | // In a real application, you would process the file uploads here 137 | // For this example, we'll just remove the file objects 138 | const applicationData = { 139 | ...validatedData, 140 | documents: { 141 | idProof: validatedData.documents.idProof ? "Uploaded" : "Not provided", 142 | incomeProof: validatedData.documents.incomeProof 143 | ? "Uploaded" 144 | : "Not provided", 145 | bankStatements: validatedData.documents.bankStatements 146 | ? "Uploaded" 147 | : "Not provided", 148 | }, 149 | }; 150 | 151 | // Save the application to the database 152 | const saveForm3 = await new Apply(validatedData).save(); 153 | // revalidatePath('/blog') 154 | console.log("Application saved:", saveForm3); 155 | return { success: true, message: "Application submitted successfully." }; 156 | 157 | // In a real application, you might want to send an email to the applicant here 158 | 159 | return { 160 | success: true, 161 | message: "Your loan application has been submitted successfully!", 162 | }; 163 | } catch (error) { 164 | console.error("Error submitting loan application:", error); 165 | return { 166 | success: false, 167 | message: 168 | "There was an error submitting your application. Please try again.", 169 | }; 170 | } 171 | } 172 | 173 | // Create post with Prisma action and server side validation 174 | const postPrismaSchema = z.object({ 175 | title: z.string().min(1).max(100), 176 | description: z.string().min(1).max(500), 177 | imageUrl: z.string().url().optional().or(z.literal("")), 178 | }); 179 | 180 | export async function createPostPrisma( 181 | data: z.infer 182 | ) { 183 | try{ 184 | const validatedData = postPrismaSchema.parse(data); 185 | 186 | const post = await prisma.postprisma.create({ 187 | data: { 188 | title: validatedData.title, 189 | description: validatedData.description, 190 | imageUrl: validatedData.imageUrl || null 191 | } 192 | }) 193 | console.log(post) 194 | return {success: true, post} 195 | }catch(error){ 196 | if(error instanceof z.ZodError){ 197 | return { success: false, message: 'Validation error', errors: error.errors } 198 | } 199 | console.error('Failed to create post:', error) 200 | return { success: false, message: 'Failed to create post' } 201 | } 202 | } 203 | 204 | // Get post with Prisma action 205 | export async function getPostsPrisma() { 206 | try { 207 | const posts = await prisma.postprisma.findMany(); 208 | return posts; 209 | } catch (error) { 210 | console.error("Error fetching posts:", error); 211 | throw new Error("Failed to fetch posts."); 212 | } 213 | } 214 | 215 | -------------------------------------------------------------------------------- /src/app/start/theme.ts: -------------------------------------------------------------------------------- 1 | export const themeJson = { 2 | "backgroundImage": "https://api.surveyjs.io/private/Surveys/files?name=fff9c109-c767-467f-b0f7-f8f1e4f5b53a", 3 | "backgroundImageFit": "cover", 4 | "backgroundImageAttachment": "fixed", 5 | "backgroundOpacity": 1, 6 | "isPanelless": true, 7 | "cssVariables": { 8 | "--sjs-general-backcolor": "rgba(255, 255, 255, 1)", 9 | "--sjs-general-backcolor-dark": "rgba(248, 248, 248, 1)", 10 | "--sjs-general-backcolor-dim": "rgba(255, 255, 255, 1)", 11 | "--sjs-general-backcolor-dim-light": "rgba(249, 249, 249, 0)", 12 | "--sjs-general-backcolor-dim-dark": "rgba(243, 243, 243, 1)", 13 | "--sjs-general-forecolor": "rgba(0, 0, 0, 0.91)", 14 | "--sjs-general-forecolor-light": "rgba(0, 0, 0, 0.45)", 15 | "--sjs-general-dim-forecolor": "rgba(0, 0, 0, 0.91)", 16 | "--sjs-general-dim-forecolor-light": "rgba(0, 0, 0, 0.45)", 17 | "--sjs-primary-backcolor": "rgba(0, 0, 0, 1)", 18 | "--sjs-primary-backcolor-light": "rgba(0, 0, 0, 0.1)", 19 | "--sjs-primary-backcolor-dark": "rgba(-15, -15, -15, 1)", 20 | "--sjs-primary-forecolor": "rgba(255, 255, 255, 1)", 21 | "--sjs-primary-forecolor-light": "rgba(255, 255, 255, 0.25)", 22 | "--sjs-base-unit": "4px", 23 | "--sjs-corner-radius": "4px", 24 | "--sjs-secondary-backcolor": "rgba(255, 152, 20, 1)", 25 | "--sjs-secondary-backcolor-light": "rgba(255, 152, 20, 0.1)", 26 | "--sjs-secondary-backcolor-semi-light": "rgba(255, 152, 20, 0.25)", 27 | "--sjs-secondary-forecolor": "rgba(255, 255, 255, 1)", 28 | "--sjs-secondary-forecolor-light": "rgba(255, 255, 255, 0.25)", 29 | "--sjs-shadow-small": "inset 0px 0px 0px 1px rgba(0, 0, 0, 0.2)", 30 | "--sjs-shadow-small-reset": "inset 0px 0px 0px 0px rgba(0, 0, 0, 0.2)", 31 | "--sjs-shadow-medium": "0px 2px 6px 0px rgba(0, 0, 0, 0.1)", 32 | "--sjs-shadow-large": "0px 8px 16px 0px rgba(0, 0, 0, 0.1)", 33 | "--sjs-shadow-inner": "0px 1px 0px 0px rgba(0, 0, 0, 0.2)", 34 | "--sjs-shadow-inner-reset": "0px 0px 0px 0px rgba(0, 0, 0, 0.2)", 35 | "--sjs-border-light": "rgba(0, 0, 0, 0.25)", 36 | "--sjs-border-default": "rgba(0, 0, 0, 0.25)", 37 | "--sjs-border-inside": "rgba(0, 0, 0, 0.16)", 38 | "--sjs-special-red": "rgba(229, 10, 62, 1)", 39 | "--sjs-special-red-light": "rgba(229, 10, 62, 0.1)", 40 | "--sjs-special-red-forecolor": "rgba(255, 255, 255, 1)", 41 | "--sjs-special-green": "rgba(25, 179, 148, 1)", 42 | "--sjs-special-green-light": "rgba(25, 179, 148, 0.1)", 43 | "--sjs-special-green-forecolor": "rgba(255, 255, 255, 1)", 44 | "--sjs-special-blue": "rgba(67, 127, 217, 1)", 45 | "--sjs-special-blue-light": "rgba(67, 127, 217, 0.1)", 46 | "--sjs-special-blue-forecolor": "rgba(255, 255, 255, 1)", 47 | "--sjs-special-yellow": "rgba(255, 152, 20, 1)", 48 | "--sjs-special-yellow-light": "rgba(255, 152, 20, 0.1)", 49 | "--sjs-special-yellow-forecolor": "rgba(255, 255, 255, 1)", 50 | "--sjs-article-font-xx-large-textDecoration": "none", 51 | "--sjs-article-font-xx-large-fontWeight": "700", 52 | "--sjs-article-font-xx-large-fontStyle": "normal", 53 | "--sjs-article-font-xx-large-fontStretch": "normal", 54 | "--sjs-article-font-xx-large-letterSpacing": "0", 55 | "--sjs-article-font-xx-large-lineHeight": "64px", 56 | "--sjs-article-font-xx-large-paragraphIndent": "0px", 57 | "--sjs-article-font-xx-large-textCase": "none", 58 | "--sjs-article-font-x-large-textDecoration": "none", 59 | "--sjs-article-font-x-large-fontWeight": "700", 60 | "--sjs-article-font-x-large-fontStyle": "normal", 61 | "--sjs-article-font-x-large-fontStretch": "normal", 62 | "--sjs-article-font-x-large-letterSpacing": "0", 63 | "--sjs-article-font-x-large-lineHeight": "56px", 64 | "--sjs-article-font-x-large-paragraphIndent": "0px", 65 | "--sjs-article-font-x-large-textCase": "none", 66 | "--sjs-article-font-large-textDecoration": "none", 67 | "--sjs-article-font-large-fontWeight": "700", 68 | "--sjs-article-font-large-fontStyle": "normal", 69 | "--sjs-article-font-large-fontStretch": "normal", 70 | "--sjs-article-font-large-letterSpacing": "0", 71 | "--sjs-article-font-large-lineHeight": "40px", 72 | "--sjs-article-font-large-paragraphIndent": "0px", 73 | "--sjs-article-font-large-textCase": "none", 74 | "--sjs-article-font-medium-textDecoration": "none", 75 | "--sjs-article-font-medium-fontWeight": "700", 76 | "--sjs-article-font-medium-fontStyle": "normal", 77 | "--sjs-article-font-medium-fontStretch": "normal", 78 | "--sjs-article-font-medium-letterSpacing": "0", 79 | "--sjs-article-font-medium-lineHeight": "32px", 80 | "--sjs-article-font-medium-paragraphIndent": "0px", 81 | "--sjs-article-font-medium-textCase": "none", 82 | "--sjs-article-font-default-textDecoration": "none", 83 | "--sjs-article-font-default-fontWeight": "400", 84 | "--sjs-article-font-default-fontStyle": "normal", 85 | "--sjs-article-font-default-fontStretch": "normal", 86 | "--sjs-article-font-default-letterSpacing": "0", 87 | "--sjs-article-font-default-lineHeight": "28px", 88 | "--sjs-article-font-default-paragraphIndent": "0px", 89 | "--sjs-article-font-default-textCase": "none", 90 | "--sjs-article-font-xx-large-fontSize": "64px", 91 | "--sjs-article-font-x-large-fontSize": "48px", 92 | "--sjs-article-font-large-fontSize": "32px", 93 | "--sjs-article-font-medium-fontSize": "24px", 94 | "--sjs-article-font-default-fontSize": "16px", 95 | "--sjs-editor-background": "rgba(249, 249, 249, 1)", 96 | "--sjs-editorpanel-backcolor": "rgba(249, 249, 249, 0)", 97 | "--sjs-editorpanel-hovercolor": "rgba(243, 243, 243, 1)", 98 | "--sjs-editorpanel-cornerRadius": "0px", 99 | "--sjs-font-size": "16px", 100 | "--font-family": "Open Sans", 101 | "--sjs-font-editorfont-color": "rgba(0, 0, 0, 1)", 102 | "--sjs-font-editorfont-placeholdercolor": "rgba(0, 0, 0, 0.35)", 103 | "--sjs-font-surveytitle-size": "24px", 104 | "--sjs-font-pagetitle-color": "rgba(0, 0, 0, 0.9)", 105 | "--sjs-font-questiontitle-color": "rgba(0, 0, 0, 0.9)", 106 | "--sjs-font-questiondescription-color": "rgba(0, 0, 0, 0.35)", 107 | "--sjs-header-backcolor": "transparent", 108 | "--sjs-font-headerdescription-color": "rgba(0, 0, 0, 0.9)", 109 | "--sjs-font-headerdescription-weight": "700", 110 | "--sjs-font-headerdescription-size": "14px" 111 | }, 112 | "themeName": "default", 113 | "colorPalette": "light", 114 | "header": { 115 | "height": 176, 116 | "inheritWidthFrom": "survey", 117 | "textAreaWidth": 360, 118 | "overlapEnabled": false, 119 | "backgroundImageOpacity": 1, 120 | "backgroundImageFit": "cover", 121 | "logoPositionX": "right", 122 | "logoPositionY": "middle", 123 | "titlePositionX": "right", 124 | "titlePositionY": "middle", 125 | "descriptionPositionX": "right", 126 | "descriptionPositionY": "middle" 127 | }, 128 | "headerView": "advanced" 129 | }; -------------------------------------------------------------------------------- /src/components/ApplicationForm/theme.ts: -------------------------------------------------------------------------------- 1 | export const themeJson = { 2 | "backgroundImage": "https://api.surveyjs.io/private/Surveys/files?name=fff9c109-c767-467f-b0f7-f8f1e4f5b53a", 3 | "backgroundImageFit": "cover", 4 | "backgroundImageAttachment": "fixed", 5 | "backgroundOpacity": 1, 6 | "isPanelless": true, 7 | "cssVariables": { 8 | "--sjs-general-backcolor": "rgba(255, 255, 255, 1)", 9 | "--sjs-general-backcolor-dark": "rgba(248, 248, 248, 1)", 10 | "--sjs-general-backcolor-dim": "rgba(255, 255, 255, 1)", 11 | "--sjs-general-backcolor-dim-light": "rgba(249, 249, 249, 0)", 12 | "--sjs-general-backcolor-dim-dark": "rgba(243, 243, 243, 1)", 13 | "--sjs-general-forecolor": "rgba(0, 0, 0, 0.91)", 14 | "--sjs-general-forecolor-light": "rgba(0, 0, 0, 0.45)", 15 | "--sjs-general-dim-forecolor": "rgba(0, 0, 0, 0.91)", 16 | "--sjs-general-dim-forecolor-light": "rgba(0, 0, 0, 0.45)", 17 | "--sjs-primary-backcolor": "rgba(0, 0, 0, 1)", 18 | "--sjs-primary-backcolor-light": "rgba(0, 0, 0, 0.1)", 19 | "--sjs-primary-backcolor-dark": "rgba(-15, -15, -15, 1)", 20 | "--sjs-primary-forecolor": "rgba(255, 255, 255, 1)", 21 | "--sjs-primary-forecolor-light": "rgba(255, 255, 255, 0.25)", 22 | "--sjs-base-unit": "4px", 23 | "--sjs-corner-radius": "4px", 24 | "--sjs-secondary-backcolor": "rgba(255, 152, 20, 1)", 25 | "--sjs-secondary-backcolor-light": "rgba(255, 152, 20, 0.1)", 26 | "--sjs-secondary-backcolor-semi-light": "rgba(255, 152, 20, 0.25)", 27 | "--sjs-secondary-forecolor": "rgba(255, 255, 255, 1)", 28 | "--sjs-secondary-forecolor-light": "rgba(255, 255, 255, 0.25)", 29 | "--sjs-shadow-small": "inset 0px 0px 0px 1px rgba(0, 0, 0, 0.2)", 30 | "--sjs-shadow-small-reset": "inset 0px 0px 0px 0px rgba(0, 0, 0, 0.2)", 31 | "--sjs-shadow-medium": "0px 2px 6px 0px rgba(0, 0, 0, 0.1)", 32 | "--sjs-shadow-large": "0px 8px 16px 0px rgba(0, 0, 0, 0.1)", 33 | "--sjs-shadow-inner": "0px 1px 0px 0px rgba(0, 0, 0, 0.2)", 34 | "--sjs-shadow-inner-reset": "0px 0px 0px 0px rgba(0, 0, 0, 0.2)", 35 | "--sjs-border-light": "rgba(0, 0, 0, 0.25)", 36 | "--sjs-border-default": "rgba(0, 0, 0, 0.25)", 37 | "--sjs-border-inside": "rgba(0, 0, 0, 0.16)", 38 | "--sjs-special-red": "rgba(229, 10, 62, 1)", 39 | "--sjs-special-red-light": "rgba(229, 10, 62, 0.1)", 40 | "--sjs-special-red-forecolor": "rgba(255, 255, 255, 1)", 41 | "--sjs-special-green": "rgba(25, 179, 148, 1)", 42 | "--sjs-special-green-light": "rgba(25, 179, 148, 0.1)", 43 | "--sjs-special-green-forecolor": "rgba(255, 255, 255, 1)", 44 | "--sjs-special-blue": "rgba(67, 127, 217, 1)", 45 | "--sjs-special-blue-light": "rgba(67, 127, 217, 0.1)", 46 | "--sjs-special-blue-forecolor": "rgba(255, 255, 255, 1)", 47 | "--sjs-special-yellow": "rgba(255, 152, 20, 1)", 48 | "--sjs-special-yellow-light": "rgba(255, 152, 20, 0.1)", 49 | "--sjs-special-yellow-forecolor": "rgba(255, 255, 255, 1)", 50 | "--sjs-article-font-xx-large-textDecoration": "none", 51 | "--sjs-article-font-xx-large-fontWeight": "700", 52 | "--sjs-article-font-xx-large-fontStyle": "normal", 53 | "--sjs-article-font-xx-large-fontStretch": "normal", 54 | "--sjs-article-font-xx-large-letterSpacing": "0", 55 | "--sjs-article-font-xx-large-lineHeight": "64px", 56 | "--sjs-article-font-xx-large-paragraphIndent": "0px", 57 | "--sjs-article-font-xx-large-textCase": "none", 58 | "--sjs-article-font-x-large-textDecoration": "none", 59 | "--sjs-article-font-x-large-fontWeight": "700", 60 | "--sjs-article-font-x-large-fontStyle": "normal", 61 | "--sjs-article-font-x-large-fontStretch": "normal", 62 | "--sjs-article-font-x-large-letterSpacing": "0", 63 | "--sjs-article-font-x-large-lineHeight": "56px", 64 | "--sjs-article-font-x-large-paragraphIndent": "0px", 65 | "--sjs-article-font-x-large-textCase": "none", 66 | "--sjs-article-font-large-textDecoration": "none", 67 | "--sjs-article-font-large-fontWeight": "700", 68 | "--sjs-article-font-large-fontStyle": "normal", 69 | "--sjs-article-font-large-fontStretch": "normal", 70 | "--sjs-article-font-large-letterSpacing": "0", 71 | "--sjs-article-font-large-lineHeight": "40px", 72 | "--sjs-article-font-large-paragraphIndent": "0px", 73 | "--sjs-article-font-large-textCase": "none", 74 | "--sjs-article-font-medium-textDecoration": "none", 75 | "--sjs-article-font-medium-fontWeight": "700", 76 | "--sjs-article-font-medium-fontStyle": "normal", 77 | "--sjs-article-font-medium-fontStretch": "normal", 78 | "--sjs-article-font-medium-letterSpacing": "0", 79 | "--sjs-article-font-medium-lineHeight": "32px", 80 | "--sjs-article-font-medium-paragraphIndent": "0px", 81 | "--sjs-article-font-medium-textCase": "none", 82 | "--sjs-article-font-default-textDecoration": "none", 83 | "--sjs-article-font-default-fontWeight": "400", 84 | "--sjs-article-font-default-fontStyle": "normal", 85 | "--sjs-article-font-default-fontStretch": "normal", 86 | "--sjs-article-font-default-letterSpacing": "0", 87 | "--sjs-article-font-default-lineHeight": "28px", 88 | "--sjs-article-font-default-paragraphIndent": "0px", 89 | "--sjs-article-font-default-textCase": "none", 90 | "--sjs-article-font-xx-large-fontSize": "64px", 91 | "--sjs-article-font-x-large-fontSize": "48px", 92 | "--sjs-article-font-large-fontSize": "32px", 93 | "--sjs-article-font-medium-fontSize": "24px", 94 | "--sjs-article-font-default-fontSize": "16px", 95 | "--sjs-editor-background": "rgba(249, 249, 249, 1)", 96 | "--sjs-editorpanel-backcolor": "rgba(249, 249, 249, 0)", 97 | "--sjs-editorpanel-hovercolor": "rgba(243, 243, 243, 1)", 98 | "--sjs-editorpanel-cornerRadius": "0px", 99 | "--sjs-font-size": "16px", 100 | "--font-family": "Open Sans", 101 | "--sjs-font-editorfont-color": "rgba(0, 0, 0, 1)", 102 | "--sjs-font-editorfont-placeholdercolor": "rgba(0, 0, 0, 0.35)", 103 | "--sjs-font-surveytitle-size": "24px", 104 | "--sjs-font-pagetitle-color": "rgba(0, 0, 0, 0.9)", 105 | "--sjs-font-questiontitle-color": "rgba(0, 0, 0, 0.9)", 106 | "--sjs-font-questiondescription-color": "rgba(0, 0, 0, 0.35)", 107 | "--sjs-header-backcolor": "transparent", 108 | "--sjs-font-headerdescription-color": "rgba(0, 0, 0, 0.9)", 109 | "--sjs-font-headerdescription-weight": "700", 110 | "--sjs-font-headerdescription-size": "14px" 111 | }, 112 | "themeName": "default", 113 | "colorPalette": "light", 114 | "header": { 115 | "height": 176, 116 | "inheritWidthFrom": "survey", 117 | "textAreaWidth": 360, 118 | "overlapEnabled": false, 119 | "backgroundImageOpacity": 1, 120 | "backgroundImageFit": "cover", 121 | "logoPositionX": "right", 122 | "logoPositionY": "middle", 123 | "titlePositionX": "right", 124 | "titlePositionY": "middle", 125 | "descriptionPositionX": "right", 126 | "descriptionPositionY": "middle" 127 | }, 128 | "headerView": "advanced" 129 | } -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { 6 | CheckIcon, 7 | ChevronRightIcon, 8 | DotFilledIcon, 9 | } from "@radix-ui/react-icons" 10 | 11 | import { cn } from "@/lib/utils" 12 | 13 | const DropdownMenu = DropdownMenuPrimitive.Root 14 | 15 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 16 | 17 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 18 | 19 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 20 | 21 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 22 | 23 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 24 | 25 | const DropdownMenuSubTrigger = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef & { 28 | inset?: boolean 29 | } 30 | >(({ className, inset, children, ...props }, ref) => ( 31 | 40 | {children} 41 | 42 | 43 | )) 44 | DropdownMenuSubTrigger.displayName = 45 | DropdownMenuPrimitive.SubTrigger.displayName 46 | 47 | const DropdownMenuSubContent = React.forwardRef< 48 | React.ElementRef, 49 | React.ComponentPropsWithoutRef 50 | >(({ className, ...props }, ref) => ( 51 | 59 | )) 60 | DropdownMenuSubContent.displayName = 61 | DropdownMenuPrimitive.SubContent.displayName 62 | 63 | const DropdownMenuContent = React.forwardRef< 64 | React.ElementRef, 65 | React.ComponentPropsWithoutRef 66 | >(({ className, sideOffset = 4, ...props }, ref) => ( 67 | 68 | 78 | 79 | )) 80 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 81 | 82 | const DropdownMenuItem = React.forwardRef< 83 | React.ElementRef, 84 | React.ComponentPropsWithoutRef & { 85 | inset?: boolean 86 | } 87 | >(({ className, inset, ...props }, ref) => ( 88 | 97 | )) 98 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 99 | 100 | const DropdownMenuCheckboxItem = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, children, checked, ...props }, ref) => ( 104 | 113 | 114 | 115 | 116 | 117 | 118 | {children} 119 | 120 | )) 121 | DropdownMenuCheckboxItem.displayName = 122 | DropdownMenuPrimitive.CheckboxItem.displayName 123 | 124 | const DropdownMenuRadioItem = React.forwardRef< 125 | React.ElementRef, 126 | React.ComponentPropsWithoutRef 127 | >(({ className, children, ...props }, ref) => ( 128 | 136 | 137 | 138 | 139 | 140 | 141 | {children} 142 | 143 | )) 144 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 145 | 146 | const DropdownMenuLabel = React.forwardRef< 147 | React.ElementRef, 148 | React.ComponentPropsWithoutRef & { 149 | inset?: boolean 150 | } 151 | >(({ className, inset, ...props }, ref) => ( 152 | 161 | )) 162 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 163 | 164 | const DropdownMenuSeparator = React.forwardRef< 165 | React.ElementRef, 166 | React.ComponentPropsWithoutRef 167 | >(({ className, ...props }, ref) => ( 168 | 173 | )) 174 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 175 | 176 | const DropdownMenuShortcut = ({ 177 | className, 178 | ...props 179 | }: React.HTMLAttributes) => { 180 | return ( 181 | 185 | ) 186 | } 187 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 188 | 189 | export { 190 | DropdownMenu, 191 | DropdownMenuTrigger, 192 | DropdownMenuContent, 193 | DropdownMenuItem, 194 | DropdownMenuCheckboxItem, 195 | DropdownMenuRadioItem, 196 | DropdownMenuLabel, 197 | DropdownMenuSeparator, 198 | DropdownMenuShortcut, 199 | DropdownMenuGroup, 200 | DropdownMenuPortal, 201 | DropdownMenuSub, 202 | DropdownMenuSubContent, 203 | DropdownMenuSubTrigger, 204 | DropdownMenuRadioGroup, 205 | } 206 | -------------------------------------------------------------------------------- /src/app/affordability/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { Button } from "@/components/ui/button"; 5 | import { Input } from "@/components/ui/input"; 6 | import { Label } from "@/components/ui/label"; 7 | import { 8 | Card, 9 | CardContent, 10 | CardDescription, 11 | CardFooter, 12 | CardHeader, 13 | CardTitle, 14 | } from "@/components/ui/card"; 15 | import { Slider } from "@/components/ui/slider"; 16 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 17 | import Container from "@/components/Container"; 18 | 19 | // Define a type for the results 20 | interface Results { 21 | maxMortgage: string; 22 | monthlyPayment: string; 23 | downPaymentPercentage: string; 24 | } 25 | 26 | export default function MortgageCalculator() { 27 | const [annualIncome, setAnnualIncome] = useState(50000); 28 | const [monthlyDebts, setMonthlyDebts] = useState(500); 29 | const [downPayment, setDownPayment] = useState(20000); 30 | const [interestRate, setInterestRate] = useState(3.5); 31 | const [loanTerm, setLoanTerm] = useState(30); 32 | const [creditScore, setCreditScore] = useState(700); 33 | const [results, setResults] = useState(null); 34 | 35 | const calculateAffordability = () => { 36 | // Monthly income 37 | const monthlyIncome = annualIncome / 12; 38 | 39 | // Maximum monthly payment (using 28/36 rule) 40 | const maxMonthlyPayment = Math.min( 41 | monthlyIncome * 0.28, 42 | monthlyIncome * 0.36 - monthlyDebts 43 | ); 44 | 45 | // Calculate maximum loan amount 46 | const monthlyInterestRate = interestRate / 100 / 12; 47 | const numberOfPayments = loanTerm * 12; 48 | const maxLoanAmount = 49 | (maxMonthlyPayment / monthlyInterestRate) * 50 | (1 - Math.pow(1 + monthlyInterestRate, -numberOfPayments)); 51 | 52 | // Total mortgage amount (including down payment) 53 | const totalMortgage = maxLoanAmount + downPayment; 54 | 55 | // Monthly mortgage payment 56 | const monthlyMortgagePayment = 57 | (maxLoanAmount * 58 | monthlyInterestRate * 59 | Math.pow(1 + monthlyInterestRate, numberOfPayments)) / 60 | (Math.pow(1 + monthlyInterestRate, numberOfPayments) - 1); 61 | 62 | setResults({ 63 | maxMortgage: totalMortgage.toFixed(2), 64 | monthlyPayment: monthlyMortgagePayment.toFixed(2), 65 | downPaymentPercentage: ((downPayment / totalMortgage) * 100).toFixed(2), 66 | }); 67 | }; 68 | 69 | return ( 70 |
71 | 72 | 73 | 74 | 75 | Mortgage Affordability Calculator 76 | 77 | 78 | Calculate how much home you can afford based on your financial 79 | situation. 80 | 81 | 82 | 83 | 84 | 85 | 86 | Basic Info 87 | 88 | 89 | Advanced Options 90 | 91 | 92 | 93 |
94 |
95 | 98 | setAnnualIncome(Number(e.target.value))} 104 | /> 105 |
106 |
107 | 110 | setMonthlyDebts(Number(e.target.value))} 116 | /> 117 |
118 |
119 | 122 | setDownPayment(Number(e.target.value))} 128 | /> 129 |
130 |
131 |
132 | 133 |
134 |
135 | 138 | setInterestRate(value[0])} 145 | /> 146 |
147 | {interestRate.toFixed(1)}% 148 |
149 |
150 |
151 | 154 | setLoanTerm(value[0])} 161 | /> 162 |
163 | {loanTerm} years 164 |
165 |
166 |
167 | 170 | setCreditScore(value[0])} 177 | /> 178 |
179 | {creditScore} 180 |
181 |
182 |
183 |
184 |
185 |
186 | 187 | 194 | {results && ( 195 |
196 |

Results

197 |
198 |
199 | Maximum Mortgage: 200 | 201 | 202 | 203 | 204 |
205 |
${results.maxMortgage}
206 |
Monthly Payment:
207 |
${results.monthlyPayment}
208 |
Down Payment:
209 |
210 | {results.downPaymentPercentage}% 211 |
212 |
213 |
214 | )} 215 |
216 |
217 |
218 |
219 | ); 220 | } 221 | -------------------------------------------------------------------------------- /src/data/json.ts: -------------------------------------------------------------------------------- 1 | export const json = { 2 | "title": "Mortgage Loan Application Form", 3 | "pages": [{ 4 | "name": "overview", 5 | "title": "Overview of your Mortgage Application", 6 | "elements": [{ 7 | "type": "radiogroup", 8 | "name": "loan-purpose", 9 | "title": "Is your mortgage application for?", 10 | "choices": [{ 11 | "value": "remortgage", 12 | "text": "Remortgage" 13 | }, { 14 | "value": "equity-transfer", 15 | "text": "Transfer of Equity" 16 | }, { 17 | "value": "house-purchase", 18 | "text": "House purchase" 19 | }, { 20 | "value": "term-extension", 21 | "text": "Term Extension" 22 | }, { 23 | "value": "further-advance", 24 | "text": "Further Advance" 25 | }], 26 | "colCount": 2 27 | }, { 28 | "type": "boolean", 29 | "name": "guarantor", 30 | "startWithNewLine": false, 31 | "title": "Is there a guarantor for this application?" 32 | }, { 33 | "type": "radiogroup", 34 | "name": "property-use", 35 | "title": "Is this property to be used for?", 36 | "choices": [{ 37 | "value": "residential", 38 | "text": "Residential" 39 | }, { 40 | "value": "buy-to-let", 41 | "text": "Buy to let" 42 | }, { 43 | "value": "holiday-let", 44 | "text": "Holiday let" 45 | }] 46 | }, { 47 | "type": "boolean", 48 | "name": "holiday-let-time-period", 49 | "visibleIf": "{property-use} = 'holiday-let'", 50 | "startWithNewLine": false, 51 | "title": "If this is an application for a holiday let, do you intend to personally use the property for more than 60 days per annum?" 52 | }] 53 | }, { 54 | "name": "property-details", 55 | "title": "Property to be mortgaged", 56 | "elements": [{ 57 | "type": "text", 58 | "name": "street-address", 59 | "title": "Street address" 60 | }, { 61 | "type": "text", 62 | "name": "city", 63 | "title": "City/Town" 64 | }, { 65 | "type": "text", 66 | "name": "zip", 67 | "startWithNewLine": false, 68 | "title": "Zip Code" 69 | }, { 70 | "type": "dropdown", 71 | "name": "country", 72 | "startWithNewLine": false, 73 | "title": "Country", 74 | "choicesByUrl": { 75 | "url": "https://surveyjs.io/api/CountriesExample" 76 | } 77 | }, { 78 | "type": "boolean", 79 | "name": "used-as-main-residence", 80 | "title": "Is this property to be used as your main residence?" 81 | }, { 82 | "type": "boolean", 83 | "name": "is-already-mortgaged", 84 | "visibleIf": "({loan-purpose} = 'house-purchase') and ('property-use'=='residental')", 85 | "startWithNewLine": false, 86 | "title": "Is there a mortgage on this property?" 87 | }, { 88 | "type": "boolean", 89 | "name": "used-for-business", 90 | "startWithNewLine": false, 91 | "title": "Will any part of the property be used for business purposes?" 92 | }] 93 | }, { 94 | "name": "personal-info", 95 | "title": "Personal Information", 96 | "elements": [ 97 | { 98 | "type": "paneldynamic", 99 | "name": "applicant-info", 100 | "titleLocation": "hidden", 101 | "defaultValue": [ 102 | {} 103 | ], 104 | "templateTitle": "Applicant #{panelIndex}", 105 | "panelAddText": "Add an applicant", 106 | "templateElements": [ 107 | { 108 | "type": "multipletext", 109 | "name": "full-name", 110 | "items": [ 111 | { 112 | "name": "first-name", 113 | "title": "First name" 114 | }, 115 | { 116 | "name": "last-name", 117 | "title": "Last name" 118 | } 119 | ] 120 | }, 121 | { 122 | "type": "multipletext", 123 | "name": "birth-info", 124 | "startWithNewLine": false, 125 | "items": [ 126 | { 127 | "name": "birthplace", 128 | "title": "Place of birth" 129 | }, 130 | { 131 | "name": "birthdate", 132 | "inputType": "date", 133 | "title": "Date of birth" 134 | } 135 | ] 136 | }, 137 | { 138 | "type": "text", 139 | "name": "phone", 140 | "title": "Phone number", 141 | "titleLocation": "top", 142 | "maskType": "pattern", 143 | "maskSettings": { 144 | "saveMaskedValue": true, 145 | "pattern": "+9 (999) 999-99-99" 146 | } 147 | }, 148 | { 149 | "type": "text", 150 | "name": "street-address", 151 | "startWithNewLine": false, 152 | "title": "Street address", 153 | "titleLocation": "top" 154 | }, 155 | { 156 | "type": "text", 157 | "name": "city", 158 | "title": "City/Town", 159 | "titleLocation": "top" 160 | }, 161 | { 162 | "type": "text", 163 | "name": "zip", 164 | "startWithNewLine": false, 165 | "title": "Zip Code", 166 | "titleLocation": "top" 167 | }, 168 | { 169 | "type": "dropdown", 170 | "name": "country", 171 | "startWithNewLine": false, 172 | "title": "Country", 173 | "titleLocation": "top", 174 | "choicesByUrl": { 175 | "url": "https://surveyjs.io/api/CountriesExample" 176 | } 177 | } 178 | ] 179 | }, 180 | { 181 | "name": "documents", 182 | "title": "Documents", 183 | "elements": [ 184 | { 185 | "type": "matrixdropdown", 186 | "name": "ids", 187 | "title": "Select two documents verifying your identity and upload their scan copies in PDF format.", 188 | "titleLocation": "top", 189 | "columns": [ 190 | { 191 | "name": "id-type", 192 | "title": "ID type", 193 | "cellType": "dropdown", 194 | "choices": [ 195 | "State identification (ID) card", 196 | "Driver license", 197 | "US passport or passport card", 198 | "US military card (front and back)", 199 | "Military dependent's ID card (front and back)", 200 | "Permanent Resident Card", 201 | "Certificate of Citizenship", 202 | "Certificate of Naturalization" 203 | ] 204 | }, 205 | { 206 | "name": "expiration-date", 207 | "title": "Expiration date", 208 | "cellType": "text", 209 | "inputType": "date" 210 | } 211 | ], 212 | "rows": [ 213 | { 214 | "value": "first-id-info", 215 | "text": "#1" 216 | }, 217 | { 218 | "value": "second-id-info", 219 | "text": "#2" 220 | } 221 | ], 222 | "rowTitleWidth": "0px" 223 | }, 224 | { 225 | "type": "file", 226 | "name": "first-id", 227 | "titleLocation": "hidden", 228 | "acceptedTypes": "application/pdf" 229 | }, 230 | { 231 | "type": "file", 232 | "name": "second-id", 233 | "startWithNewLine": false, 234 | "titleLocation": "hidden", 235 | "acceptedTypes": "application/pdf" 236 | } 237 | ] 238 | } 239 | ] 240 | }, { 241 | "name": "employment-details", 242 | "title": "Employment details", 243 | "elements": [{ 244 | "type": "paneldynamic", 245 | "name": "employer", 246 | "startWithNewLine": false, 247 | "titleLocation": "hidden", 248 | "panelAddText": "Add an employer", 249 | "defaultValue": [ 250 | {} 251 | ], 252 | "templateElements": [{ 253 | "type": "comment", 254 | "name": "organization", 255 | "title": "Name of Organization", 256 | "titleLocation": "top", 257 | "placeholder": "Enter the name of your current employer" 258 | }, { 259 | "type": "multipletext", 260 | "name": "occupation", 261 | "items": [{ 262 | "name": "position", 263 | "placeholder": "Enter your current position in the company", 264 | "title": "Occupation" 265 | }, { 266 | "name": "employment-date", 267 | "inputType": "date", 268 | "title": "Employed since" 269 | }] 270 | }, { 271 | "type": "panel", 272 | "name": "income-panel", 273 | "elements": [{ 274 | "type": "multipletext", 275 | "name": "income", 276 | "title": "Monthly Income in USD", 277 | "titleLocation": "top", 278 | "items": [{ 279 | "name": "basic-salary", 280 | "title": "Basic salary" 281 | }, { 282 | "name": "guaranteed-bonus", 283 | "title": "Guaranteed bonus" 284 | }, { 285 | "name": "nonguaranteed-bonus", 286 | "title": "Non-guaranteed bonus" 287 | }] 288 | }] 289 | }, { 290 | "type": "panel", 291 | "name": "other-income-panel", 292 | "elements": [{ 293 | "type": "boolean", 294 | "name": "have-other-income-sources", 295 | "title": "Any other regular income?", 296 | "titleLocation": "top" 297 | }, { 298 | "type": "text", 299 | "name": "other-income-sources", 300 | "placeholder": "Please specify..." 301 | }], 302 | "startWithNewLine": false 303 | }, { 304 | "type": "file", 305 | "name": "employment-verification-letter", 306 | "title": "Letter of Employment Verification", 307 | "description": "Please upload a letter of employment verification signed by your current employer.", 308 | "titleLocation": "top" 309 | }] 310 | }] 311 | }, { 312 | "name": "loan-details", 313 | "title": "Requested Loan Details", 314 | "elements": [{ 315 | "type": "text", 316 | "name": "loan-amount", 317 | "title": "Requested loan amount in USD", 318 | "maskType": "currency", 319 | "maskSettings": { 320 | "prefix": "$" 321 | }, 322 | "placeholder": "$1,000,000" 323 | }, { 324 | "type": "text", 325 | "name": "loan-tenure", 326 | "startWithNewLine": false, 327 | "title": "Loan tenure in years", 328 | "inputType": "number", 329 | "min": 1, 330 | "max": 25 331 | }] 332 | }], 333 | "showQuestionNumbers": false, 334 | "completeText": "Submit", 335 | "widthMode": "static", 336 | "width": "1200px" 337 | }; --------------------------------------------------------------------------------