commit 717a3be1c84cd70a0a9f8a4a4ae9b54a3f9cf5c8 Author: jwinterm Date: Thu Jan 15 15:43:47 2026 -0500 feat: Initial Monorepo Setup for Such Software - **Architecture**: Initialized Turborepo monorepo with pnpm workspace. - **Frontend**: Scaffolding for Next.js 14 app (apps/web) with Tailwind CSS v4 configuration. - **UI System**: Created shared UI package (@repo/ui) with Shadcn/UI primitives (Card, Button). - **Procedural Design**: - Implemented 'NebulaField' organic background animation for Home. - Implemented 'DataGrid' isometric background for Services. - Added 'DynamicBackground' for theme-aware transitions. - **Features**: - Responsive 'ThemeToggle' with View Transitions API support. - 'PortfolioGrid' component showcasing key projects (r/CryptoCurrency, etc.). - 'FounderSignature' component detailing accessibility focus. - 'ContactForm' with Zod validation + turnstile integration stub. - **Pages**: - Landing page with parallax/procedural visuals. - Services page highlighting Medusa v2 development capabilities. - **Clean Up**: Removed initial Medusa backend scaffold to focus on frontend delivery first. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be93fa7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# Dependencies +node_modules +.pnpm-store + +# Next.js +.next/ +out/ +build/ +dist/ + +# Turborepo +.turbo/ + +# Environment configurations +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +*.env +!.env.example + +# Vercel +.vercel + +# VS Code +.vscode/* +!.vscode/extensions.json +!.vscode/launch.json +!.vscode/tasks.json +!.vscode/settings.json +*.code-workspace + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# OS +.DS_Store +Thumbs.db + +# TypeScript +*.tsbuildinfo +next-env.d.ts + +# Misc +.cache/ +coverage/ + +# AI / Assistant +.claude/ diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.js new file mode 100644 index 0000000..2e19b2e --- /dev/null +++ b/apps/web/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ["@repo/config/eslint-preset.js"], +}; \ No newline at end of file diff --git a/apps/web/next.config.js b/apps/web/next.config.js new file mode 100644 index 0000000..c147ce5 --- /dev/null +++ b/apps/web/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + transpilePackages: ["@repo/ui"], +}; + +module.exports = nextConfig; \ No newline at end of file diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..0479f7b --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,35 @@ +{ + "name": "web", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@marsidev/react-turnstile": "^1.0.2", + "@repo/config": "workspace:*", + "@repo/ui": "workspace:*", + "framer-motion": "^11.0.0", + "lucide-react": "^0.350.0", + "next": "14.1.0", + "next-themes": "^0.2.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "autoprefixer": "^10.0.1", + "eslint": "^8", + "eslint-config-next": "14.1.0", + "postcss": "^8", + "tailwindcss": "^3.3.0", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5" + } +} \ No newline at end of file diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js new file mode 100644 index 0000000..8567b4c --- /dev/null +++ b/apps/web/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; \ No newline at end of file diff --git a/apps/web/src/actions/submit-contact.ts b/apps/web/src/actions/submit-contact.ts new file mode 100644 index 0000000..f7a3543 --- /dev/null +++ b/apps/web/src/actions/submit-contact.ts @@ -0,0 +1,41 @@ +'use server'; + +import { z } from 'zod'; +import { verifyTurnstile } from '@/lib/turnstile'; // Helper to call Cloudflare API + +const ContactSchema = z.object({ + name: z.string().min(2, "Name is too short"), + email: z.string().email("Invalid email address"), + message: z.string().min(10, "Please tell us more about your project"), + token: z.string().min(1, "Security check failed") +}); + +export async function submitContactForm(prevState: any, formData: FormData) { + const rawData = Object.fromEntries(formData.entries()); + + // 1. Zod Validation + const validated = ContactSchema.safeParse(rawData); + if (!validated.success) { + return { success: false, errors: validated.error.flatten().fieldErrors }; + } + + // 2. Turnstile Verification + const isHuman = await verifyTurnstile(validated.data.token); + if (!isHuman) { + return { success: false, message: "Security check failed. Please refresh." }; + } + + // 3. Send to Backend (or Email) + try { + // TODO: Connect to email service or CMS + console.log("Form submitted:", validated.data); + + // Simulating success for now + await new Promise(resolve => setTimeout(resolve, 500)); + + return { success: true, message: "Request received. We will be in touch shortly." }; + } catch (e) { + console.error(e); + return { success: false, message: "System error. Please contact us via email." }; + } +} \ No newline at end of file diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css new file mode 100644 index 0000000..36636bb --- /dev/null +++ b/apps/web/src/app/globals.css @@ -0,0 +1,76 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx new file mode 100644 index 0000000..d863783 --- /dev/null +++ b/apps/web/src/app/layout.tsx @@ -0,0 +1,32 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; +import { ThemeProvider } from "@/components/theme-provider"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Such Software LLC", + description: "Next-Generation Monorepo Platform", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + {children} + + + + ); +} \ No newline at end of file diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx new file mode 100644 index 0000000..8422bc7 --- /dev/null +++ b/apps/web/src/app/page.tsx @@ -0,0 +1,61 @@ +import { DynamicBackground } from "@/components/landing/dynamic-background"; +import { ThemeToggle } from "@/components/theme-toggle"; +import { PortfolioGrid } from "@/components/portfolio/bento-grid"; +import { ContactForm } from "@/components/contact/contact-form"; +import { FounderSignature } from "@/components/about/founder-signature"; +import Link from "next/link"; +import { Button } from "@repo/ui/components/button"; + +export default function Home() { + return ( +
+ + + +
+
+

Such Software

+ +
+ +
+ +
+

+ Digital Craftsmanship +

+

+ Next-generation web applications engineered for scale, compliance, and performance. +

+ +
+ + + + + + +
+ +
+

Selected Works

+ +
+ + + +
+

Start a Project

+ +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/web/src/app/services/page.tsx b/apps/web/src/app/services/page.tsx new file mode 100644 index 0000000..b43a853 --- /dev/null +++ b/apps/web/src/app/services/page.tsx @@ -0,0 +1,75 @@ +import { DataGrid } from "@repo/ui/procedural/data-grid"; +import { ThemeToggle } from "@/components/theme-toggle"; +import { Button } from "@repo/ui/components/button"; +import Link from "next/link"; +import { Card } from "@repo/ui/components/card"; + +export default function ServicesPage() { + return ( +
+ + {/* Option B: Data Grid Background for Technical Feel */} + + +
+
+ Such Software + +
+ +
+ +
+
+

+ Medusa v2 Development +

+

+ We build proprietary, headless commerce engines that scale with your business logic. + Stop fighting with Shopify's limitations. +

+
+ +
+ +

Unlimited Data Modeling

+

+ Need a product with "DNA Sequence", "Artist Bio", and "Royalty Logic"? + We model that natively in the database, not via hacky meta-fields. +

+
+ +

Workflow Orchestration

+

+ Automate complex business logic—like splitting an order between a print-on-demand provider + and a local warehouse automatically using Medusa Workflows. +

+
+ +

Headless Performance

+

+ We couple the robust Medusa backend with a Next.js 16 frontend, ensuring your shop + scores 100/100 on Core Web Vitals and SEO. +

+
+
+ +
+

Ready to upgrading your shop?

+

+ We are currently accepting beta partners for our new Medusa v2 accelerator. +

+ + + +
+
+ +
+ ); +} \ No newline at end of file diff --git a/apps/web/src/components/about/founder-signature.tsx b/apps/web/src/components/about/founder-signature.tsx new file mode 100644 index 0000000..1b290db --- /dev/null +++ b/apps/web/src/components/about/founder-signature.tsx @@ -0,0 +1,48 @@ +import { Card } from "@repo/ui/components/card"; + +export const FounderSignature = () => { + return ( +
+
+
+

Accessibility as Architecture

+
+

+ "During my three years at Cecil College, I didn't just teach web design—I re-engineered how it was taught. +

+

+ Transitioning the curriculum toward 'Digital Arts and Media Design,' I embedded inclusive design principles into the foundation of every course. I taught students that a website isn't truly 'live' until it is accessible to everyone. +

+

+ This wasn't just academic theory; it was a rigorous discipline of semantic HTML, ARIA compliance, and cognitive load management. Today, I bring that same academic rigor to Such Software’s commercial projects. We don't retroactively fix accessibility; we architect for it from line one." +

+
+
+

Founder & Principle Engineer

+
+
+ + +
+
+ +

Core Competencies

+
    +
  • + ✓ WCAG 2.2 AA Compliance +
  • +
  • + ✓ Section 508 & ADA +
  • +
  • + ✓ Semantic HTML5 Architecture +
  • +
  • + ✓ Playwright A11y Automation +
  • +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/apps/web/src/components/contact/contact-form.tsx b/apps/web/src/components/contact/contact-form.tsx new file mode 100644 index 0000000..aa12793 --- /dev/null +++ b/apps/web/src/components/contact/contact-form.tsx @@ -0,0 +1,58 @@ +'use client'; +import { useFormState, useFormStatus } from 'react-dom'; // Note: check react-dom version for useFormState support or import from hook +// Next.js 14 uses react-dom for these +import { submitContactForm } from '@/actions/submit-contact'; +import { TurnstileWidget } from './turnstile-widget'; +import { useState } from 'react'; +import { Button } from '@repo/ui/components/button'; + +function SubmitButton() { + const { pending } = useFormStatus(); + return ( + + ); +} + +export function ContactForm() { + const [state, formAction] = useFormState(submitContactForm, { success: false, message: '' }); + const [token, setToken] = useState(''); + + return ( +
+

Contact Us

+
+ + + {state?.errors?.name &&

{state.errors.name}

} +
+ +
+ + + {state?.errors?.email &&

{state.errors.email}

} +
+ +
+ +