Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening

Batch commit of the pending working tree on security/audit-fixes-2026-07.
Major areas:
- Outbound webhooks / Zapier: schema + signed delivery with retries, public
  v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain.
- Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS
  (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when
  Spaces is unconfigured instead of silently using ephemeral disk.
- Integrations & features (concurrent work): accounting (QuickBooks/Xero),
  e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding,
  expanded legal pages.
- DB migrations 0006–0009.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-07-02 13:42:34 -04:00
co-authored by Claude Opus 4.8
parent 969d5d4c8a
commit c9968531e4
282 changed files with 41530 additions and 4013 deletions
+101
View File
@@ -0,0 +1,101 @@
import { LegalPage, Section, LegalContact } from "@/components/marketing/legal"
import { LEGAL } from "@/lib/legal"
export const metadata = {
title: "Acceptable Use Policy",
description: `The rules that govern acceptable use of the ${LEGAL.service} platform and the activities that are prohibited.`,
alternates: { canonical: "/acceptable-use" },
}
export default function Page() {
return (
<LegalPage
title="Acceptable Use Policy"
subtitle={`This Acceptable Use Policy sets out the rules for using the ${LEGAL.service} platform. It is designed to keep the Service safe, lawful, and reliable for everyone.`}
>
<Section heading="1. Overview and scope">
<p>
This Acceptable Use Policy (the <strong>Policy</strong>) applies to all access to and use of the{" "}
{LEGAL.service} platform (the <strong>Service</strong>) operated by {LEGAL.entity} (<strong>we</strong>,{" "}
<strong>us</strong>, or <strong>our</strong>). It applies to you as the account holder (a landlord or
property manager), whom we refer to as <strong>you</strong> or <strong>Customer</strong>, and to anyone who
accesses the Service through your account.
</p>
<p>
This Policy forms part of, and is incorporated by reference into, our{" "}
<a href="/terms">Terms of Service</a>. Capitalized terms that are not defined here have the meaning given
to them in the Terms. Violating this Policy is a violation of the Terms and may result in enforcement action
as described below.
</p>
</Section>
<Section heading="2. Prohibited activities">
<p>When using the Service, you must not, and must not permit any third party to:</p>
<ul>
<li>Use the Service for any illegal, fraudulent, or unauthorized purpose, or in any way that violates applicable law or regulation.</li>
<li>Violate any housing, fair-housing, anti-discrimination, or landlord-tenant laws, including using the Service to discriminate against any Tenant or applicant on a protected basis.</li>
<li>Harass, threaten, defame, or otherwise engage in abusive or unlawful contact with any <strong>Tenant</strong> or other individual through the Service.</li>
<li>Upload, store, or transmit content that is infringing, unlawful, defamatory, obscene, or that contains viruses, malware, or other malicious code.</li>
<li>Gain or attempt to gain unauthorized access to the Service, to other accounts, or to any systems or networks connected to the Service, including through scraping or automated bulk access.</li>
<li>Probe, scan, or test the vulnerability of the Service, or circumvent, disable, or otherwise interfere with any security features or access controls.</li>
<li>Reverse engineer, decompile, or disassemble any part of the Service, or attempt to derive its source code, except to the extent this restriction is prohibited by applicable law.</li>
<li>Interfere with, disrupt, or place an unreasonable load on the Service or its infrastructure, or attempt to overload or degrade it.</li>
<li>Use the email or notification features of the Service to send spam, unsolicited bulk messages, or any unlawful, deceptive, or harassing communications.</li>
<li>Misuse <strong>Tenant</strong> personal data, including using it for any purpose other than the lawful management of the relevant tenancy or in a manner inconsistent with your obligations to that Tenant.</li>
</ul>
</Section>
<Section heading="3. Data and privacy responsibilities">
<p>
You may only upload or process <strong>Tenant</strong> personal data, or any other personal data, for which
you have a lawful basis and, where required, the necessary consent. You must not upload special-category or
sensitive personal data unless doing so is lawful and appropriate for your purposes.
</p>
<p>
For personal data you process through the Service, you act as the <strong>data controller</strong> and are
responsible for complying with applicable data-protection laws. Our respective obligations are set out in
our <a href="/dpa">Data Processing Addendum</a>, and our data practices are described in our{" "}
<a href="/privacy">Privacy Policy</a>.
</p>
</Section>
<Section heading="4. Security and vulnerability reporting">
<p>
You must not conduct any unauthorized security testing against the Service. If you discover a security
vulnerability or a potential weakness, please report it responsibly to{" "}
<a href={`mailto:${LEGAL.securityEmail}`}>{LEGAL.securityEmail}</a> and give us a reasonable opportunity to
investigate and remediate it before disclosing it to others. We appreciate good-faith reports and will not
pursue action against researchers who act responsibly and within the law.
</p>
</Section>
<Section heading="5. Usage limits and fair use">
<p>
You must respect the usage limits, quotas, and rate limits associated with your plan, and you must use the
Service in a manner consistent with fair use. You must not abuse, circumvent, or attempt to exceed any API
rate limits, or use automated means to consume a disproportionate share of resources. We may apply
technical or contractual measures to protect the Service and other Customers from excessive or abusive use.
</p>
</Section>
<Section heading="6. Enforcement">
<p>
We may investigate suspected violations of this Policy and take any action we consider appropriate,
including removing or disabling access to offending content, throttling usage, suspending or terminating
accounts, and cooperating with law-enforcement authorities or other third parties. We may act with or
without notice depending on the severity of the violation and the risk to the Service or others. Suspension
and termination are further described in our <a href="/terms">Terms of Service</a>.
</p>
</Section>
<Section heading="7. Reporting abuse">
<p>
If you become aware of any use of the Service that violates this Policy, please report it to us so that we
can investigate.
</p>
</Section>
<LegalContact email={LEGAL.contactEmail} />
</LegalPage>
)
}
+110 -28
View File
@@ -1,20 +1,29 @@
import Link from "next/link"
import { ArrowRight, Code2, Lock, Zap, BookOpen } from "lucide-react"
import { Code2, Lock, Zap, BookOpen, Webhook } from "lucide-react"
import { WEBHOOK_EVENTS } from "@/lib/webhooks/events"
export const metadata = {
title: "API Docs — Property Management Network",
title: "API Docs",
description: "Property Management Network REST API documentation for developers.",
alternates: { canonical: "/api-docs" },
}
// The real, deployed origin. Falls back to a placeholder only when the env var
// isn't set (e.g. local docs previews).
const BASE_URL = `${process.env.NEXT_PUBLIC_APP_URL ?? "https://your-app-url"}/api/v1`
const ENDPOINTS = [
{ method: "GET", path: "/api/properties", desc: "List all properties for the authenticated landlord" },
{ method: "POST", path: "/api/properties", desc: "Create a new property" },
{ method: "GET", path: "/api/tenants", desc: "List all tenants with lease status" },
{ method: "GET", path: "/api/payments", desc: "List rent payments with filters (status, date range)" },
{ method: "POST", path: "/api/payments", desc: "Record a new payment manually" },
{ method: "GET", path: "/api/maintenance", desc: "List all maintenance requests" },
{ method: "POST", path: "/api/maintenance", desc: "Create a maintenance request" },
{ method: "PATCH", path: "/api/maintenance/:id", desc: "Update request status or assign to contractor" },
{ method: "GET", path: "/properties", desc: "List all properties for the authenticated account" },
{ method: "POST", path: "/properties", desc: "Create a new property" },
{ method: "GET", path: "/tenants", desc: "List tenants with their unit and lease status" },
{ method: "GET", path: "/payments", desc: "List rent payments (filter by status, tenant_id, from/to date range)" },
{ method: "POST", path: "/payments", desc: "Record a rent payment" },
{ method: "GET", path: "/maintenance", desc: "List maintenance requests (filter by status, priority, property_id)" },
{ method: "POST", path: "/maintenance", desc: "Create a maintenance request" },
{ method: "PATCH", path: "/maintenance/:id", desc: "Update a maintenance request's status or fields" },
{ method: "GET", path: "/webhooks", desc: "List webhook subscriptions" },
{ method: "POST", path: "/webhooks", desc: "Create a webhook subscription (Zapier REST Hook subscribe)" },
{ method: "DELETE", path: "/webhooks/:id", desc: "Delete a webhook subscription (Zapier REST Hook unsubscribe)" },
]
const METHOD_COLORS: Record<string, string> = {
@@ -41,7 +50,7 @@ export default function ApiDocsPage() {
</p>
<div className="mt-6 inline-flex items-center gap-2 rounded-xl border border-white/10 bg-white/[0.04] px-4 py-2.5">
<span className="text-xs font-mono text-white/40">Base URL:</span>
<code className="text-xs font-mono text-indigo-300">https://api.propertymanagement.network/v1</code>
<code className="text-xs font-mono text-indigo-300">{BASE_URL}</code>
</div>
</div>
</div>
@@ -54,13 +63,15 @@ export default function ApiDocsPage() {
</h2>
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] p-6">
<p className="text-sm text-white/60 mb-4 leading-relaxed">
All API requests require a Bearer token in the Authorization header. Generate your API key from
the <Link href="/login" className="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">dashboard settings</Link>.
All API requests require a Bearer API key in the Authorization header. Keys look like{" "}
<code className="text-indigo-300 font-mono text-xs">pmn_live_</code> and are generated from{" "}
<Link href="/login" className="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">Settings API keys</Link>{" "}
inside your dashboard. The plaintext key is shown only once at creation, so store it securely.
</p>
<div className="rounded-xl bg-[#0a0a12] border border-white/[0.06] p-4 font-mono text-xs text-emerald-300">
<p className="text-white/30 mb-1"># Example request</p>
<p>curl https://api.propertymanagement.network/v1/properties \</p>
<p className="pl-4">-H &quot;Authorization: Bearer YOUR_API_KEY&quot;</p>
<p>curl {BASE_URL}/properties \</p>
<p className="pl-4">-H &quot;Authorization: Bearer pmn_live_...&quot;</p>
</div>
</div>
</div>
@@ -91,27 +102,98 @@ export default function ApiDocsPage() {
<BookOpen className="h-5 w-5 text-blue-400" /> Response Format
</h2>
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] p-6">
<p className="text-sm text-white/60 mb-4">All responses are JSON. Successful responses return a <code className="text-indigo-300 font-mono text-xs">data</code> field. Errors return an <code className="text-red-300 font-mono text-xs">error</code> field with a message and code.</p>
<p className="text-sm text-white/60 mb-4">
All responses are JSON. List endpoints return a <code className="text-indigo-300 font-mono text-xs">data</code> array
with a <code className="text-indigo-300 font-mono text-xs">count</code>. Single-record and create responses return a{" "}
<code className="text-indigo-300 font-mono text-xs">data</code> object (create returns HTTP 201). Errors return an{" "}
<code className="text-red-300 font-mono text-xs">error</code> object with a numeric <code className="text-red-300 font-mono text-xs">code</code> and a <code className="text-red-300 font-mono text-xs">message</code>.
</p>
<div className="rounded-xl bg-[#0a0a12] border border-white/[0.06] p-4 font-mono text-xs leading-relaxed">
<p className="text-white/30">// Success</p>
<p className="text-white/30">{"// Success (list)"}</p>
<p className="text-emerald-300">{"{"} &quot;data&quot;: [...], &quot;count&quot;: 12 {"}"}</p>
<br />
<p className="text-white/30">// Error</p>
<p className="text-white/30">{"// Success (single / create)"}</p>
<p className="text-emerald-300">{"{"} &quot;data&quot;: {"{"} ... {"}"} {"}"}</p>
<br />
<p className="text-white/30">{"// Error"}</p>
<p className="text-red-300">{"{"} &quot;error&quot;: {"{"} &quot;code&quot;: 401, &quot;message&quot;: &quot;Unauthorized&quot; {"}"} {"}"}</p>
</div>
</div>
</div>
{/* Coming soon banner */}
<div className="rounded-2xl border border-indigo-500/20 bg-indigo-500/5 p-6 text-center">
<p className="text-sm font-semibold text-indigo-300 mb-1">Full SDK coming soon</p>
<p className="text-xs text-white/40 mb-4">We&apos;re building official JavaScript and Python SDKs. Join the waitlist to be notified.</p>
<Link
href="/signup"
className="inline-flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-2 text-xs font-semibold text-white hover:bg-indigo-500 transition"
>
Join waitlist <ArrowRight className="h-3.5 w-3.5" />
</Link>
{/* Webhooks */}
<div id="webhooks" className="scroll-mt-24">
<h2 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<Webhook className="h-5 w-5 text-emerald-400" /> Webhooks
</h2>
<div className="space-y-4">
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] p-6">
<p className="text-sm text-white/60 leading-relaxed">
Subscribe to real-time events instead of polling. Add endpoints in{" "}
<Link href="/login" className="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">Settings Webhooks</Link>{" "}
(or via the <code className="text-indigo-300 font-mono text-xs">/webhooks</code> API), and we&apos;ll POST a
signed JSON payload the moment something happens. This is the same mechanism that powers our{" "}
<span className="text-white/80 font-medium">Zapier</span> integration Zapier subscribes and unsubscribes
through the <code className="text-indigo-300 font-mono text-xs">POST /webhooks</code> and{" "}
<code className="text-indigo-300 font-mono text-xs">DELETE /webhooks/:id</code> endpoints (the REST Hook pattern).
</p>
</div>
{/* Events */}
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] overflow-hidden">
<div className="border-b border-white/[0.06] px-6 py-3">
<p className="text-xs font-semibold uppercase tracking-widest text-white/40">Available events</p>
</div>
{WEBHOOK_EVENTS.map((ev, i) => (
<div
key={ev.id}
className={`flex items-start gap-4 px-6 py-3 ${i !== WEBHOOK_EVENTS.length - 1 ? "border-b border-white/[0.04]" : ""}`}
>
<code className="shrink-0 rounded-md bg-emerald-500/10 px-2.5 py-1 text-[11px] font-bold font-mono text-emerald-400">
{ev.id}
</code>
<p className="text-xs text-white/50 mt-0.5">{ev.description}</p>
</div>
))}
</div>
{/* Payload + signature */}
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] p-6">
<p className="text-sm font-semibold text-white mb-2">Payload &amp; signature</p>
<p className="text-sm text-white/60 mb-4 leading-relaxed">
Each request body is a JSON envelope. Every delivery carries an{" "}
<code className="text-indigo-300 font-mono text-xs">X-PMN-Signature</code> header {" "}
<code className="text-indigo-300 font-mono text-xs">t=&lt;unix&gt;,v1=&lt;hex&gt;</code> where{" "}
<code className="text-indigo-300 font-mono text-xs">v1</code> is the HMAC-SHA256 of{" "}
<code className="text-indigo-300 font-mono text-xs">{"`${t}.${rawBody}`"}</code> keyed with your endpoint&apos;s
signing secret. Recompute it and compare in constant time; reject if the timestamp is stale.
</p>
<div className="rounded-xl bg-[#0a0a12] border border-white/[0.06] p-4 font-mono text-xs leading-relaxed text-white/70">
<p className="text-white/30">{"// POST body"}</p>
<p className="text-emerald-300">{"{"}</p>
<p className="pl-4">&quot;id&quot;: &quot;evt_9f2c&quot;,</p>
<p className="pl-4">&quot;event&quot;: &quot;tenant.created&quot;,</p>
<p className="pl-4">&quot;created_at&quot;: &quot;2026-07-02T12:00:00.000Z&quot;,</p>
<p className="pl-4">&quot;data&quot;: {"{"} &quot;tenant&quot;: {"{"} {"}"} {"}"}</p>
<p className="text-emerald-300">{"}"}</p>
<p className="mt-2 text-white/30">{"# Headers"}</p>
<p>X-PMN-Event: tenant.created</p>
<p>X-PMN-Delivery: &lt;delivery id&gt;</p>
<p>X-PMN-Signature: t=1751457600,v1=1a2b3c</p>
</div>
<p className="text-xs text-white/40 mt-4">
Respond with any <span className="text-white/70">2xx</span> to acknowledge. Non-2xx or timeouts are retried
with exponential backoff (up to 5 attempts); a test event is available from the dashboard.
</p>
</div>
</div>
</div>
{/* SDK note */}
<div className="rounded-2xl border border-white/[0.06] bg-white/[0.02] p-6 text-center">
<p className="text-xs text-white/40">
Official client SDKs are not yet available. Call the endpoints directly over HTTP with any language or HTTP client.
</p>
</div>
</div>
</div>
+111 -59
View File
@@ -1,77 +1,129 @@
import { LegalPage, Section, LegalContact } from "@/components/marketing/legal"
import { LEGAL } from "@/lib/legal"
export const metadata = {
title: "Cookie Policy — Property Management Network",
description: "How Property Management Network uses cookies and similar tracking technologies.",
title: "Cookie Policy",
description:
"How we use cookies and similar technologies, what we deliberately avoid, and how to manage them in your browser.",
alternates: { canonical: "/cookie-policy" },
}
const SECTIONS = [
const COOKIE_ROWS: { name: string; type: string; purpose: string; expires: string }[] = [
{
title: "What are cookies?",
body: "Cookies are small text files stored on your device by your browser when you visit a website. They help websites remember your preferences, keep you logged in, and understand how the site is being used.",
name: "session",
type: "Strictly necessary",
purpose: "Keeps you signed in and maintains your authenticated session",
expires: "On sign-out or after inactivity",
},
{
title: "Cookies we use",
body: "We use the following types of cookies: (1) Essential cookies — required for the application to function, such as session authentication tokens. Without these, you cannot log in. (2) Analytics cookies — we use Vercel Analytics (privacy-preserving, no personal data stored) to understand page performance. (3) Preference cookies — we store your dashboard preferences (dark/light mode, column visibility) in browser localStorage.",
name: "preferences (localStorage)",
type: "Preference",
purpose: "Stores interface preferences such as layout and theme in your browser",
expires: "Persistent until cleared",
},
{
title: "Third-party cookies",
body: "Stripe may set cookies when you make a payment for fraud prevention and PCI compliance purposes. We do not use advertising, retargeting, or social media tracking cookies.",
},
{
title: "How to control cookies",
body: "You can manage cookies through your browser settings. Most browsers allow you to block or delete cookies. Note that blocking essential cookies will prevent you from logging in to Property Management Network. For analytics cookies, you can opt out by enabling the Do Not Track header in your browser.",
},
{
title: "Cookie retention",
body: "Session cookies expire when you close your browser. Authentication tokens are refreshed automatically and expire after 7 days of inactivity. Analytics data is retained for 90 days in aggregate, with no individual identifiers stored.",
},
{
title: "Changes to this policy",
body: "We may update this Cookie Policy as we add new features. Significant changes will be announced via the in-app notification banner. The date at the bottom of this page reflects the most recent update.",
},
{
title: "Contact",
body: "For questions about cookies or this policy, contact us at privacy@propertymanagement.network.",
name: "__stripe_*",
type: "Strictly necessary (Stripe)",
purpose: "Set by Stripe to prevent payment fraud during checkout",
expires: "Session or up to one year",
},
]
export default function CookiePolicyPage() {
export default function Page() {
return (
<div className="bg-[#09090b] text-white min-h-screen">
<div className="mx-auto max-w-3xl px-6 pt-32 pb-24">
<div className="mb-10">
<h1 className="text-3xl font-bold text-white mb-2">Cookie Policy</h1>
<p className="text-xs text-white/30">Last updated: April 2026</p>
</div>
<LegalPage
title="Cookie Policy"
subtitle="This policy explains how we use cookies and similar technologies within the Service."
>
<Section heading="What are cookies">
<p>
Cookies are small text files that a website stores on your device through your browser.
Similar technologies, such as browser <strong>localStorage</strong>, allow a site to store
data locally in a comparable way. These technologies help a website keep you signed in,
remember your preferences, and operate securely. This policy describes how{" "}
<strong>{LEGAL.entity}</strong> uses them within <strong>{LEGAL.service}</strong> (the{" "}
<strong>Service</strong>).
</p>
</Section>
<div className="space-y-8">
{SECTIONS.map((s) => (
<div key={s.title}>
<h2 className="text-base font-semibold text-white mb-2">{s.title}</h2>
<p className="text-sm text-white/50 leading-relaxed">{s.body}</p>
</div>
))}
</div>
<Section heading="Cookies and similar technologies we use">
<p>We use only the following limited set of technologies:</p>
<ul>
<li>
<strong>Strictly necessary authentication and session cookies</strong>&mdash;required to
sign you in and to maintain your secure session. The Service cannot function without
these.
</li>
<li>
<strong>Preference storage in browser localStorage</strong>&mdash;used to remember
interface preferences, such as layout and theme, on your device.
</li>
<li>
<strong>Stripe cookies</strong>&mdash;set by Stripe during payment to help prevent fraud
and to support secure checkout.
</li>
<li>
<strong>Cloudflare Turnstile challenge cookie</strong>&mdash;a challenge cookie that may
be set on authentication pages to distinguish genuine users from automated bots.
</li>
</ul>
</Section>
{/* Cookie types summary table */}
<div className="mt-10 rounded-2xl border border-white/[0.06] bg-[#111118] overflow-hidden">
<div className="px-5 py-3.5 border-b border-white/[0.04]">
<p className="text-xs font-semibold uppercase tracking-wider text-white/30">Cookie Summary</p>
<Section heading="What we do not use">
<p>
We do <strong>not</strong> use advertising, retargeting, or cross-site tracking cookies, and
we do <strong>not</strong> use third-party analytics cookies. We do not build advertising
profiles or share cookie data with advertising networks.
</p>
</Section>
<Section heading="Managing cookies">
<p>
Most browsers allow you to view, block, or delete cookies through their settings. Because
our authentication and session cookies are <strong>strictly necessary</strong>, blocking
them will prevent you from signing in to and using the Service. You can adjust your browser
settings at any time to control non-essential storage.
</p>
</Section>
<Section heading="Retention">
<p>
<strong>Session</strong> cookies are temporary and are cleared when your session ends, while{" "}
<strong>persistent</strong> storage remains on your device until it expires or you remove
it. Authentication sessions expire after a period of inactivity, after which you will be
asked to sign in again.
</p>
</Section>
<Section heading="Changes">
<p>
We may update this Cookie Policy as the Service evolves. When we make material changes, we
will update the date shown above. Please review this page periodically to stay informed.
</p>
</Section>
<LegalContact email={LEGAL.privacyEmail} />
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] overflow-hidden">
<div className="px-5 py-3.5 border-b border-white/[0.04]">
<p className="text-xs font-semibold uppercase tracking-wider text-white/30">
Cookie summary
</p>
</div>
{COOKIE_ROWS.map((row, i, arr) => (
<div
key={row.name}
className={`grid grid-cols-1 gap-1 px-5 py-3.5 text-xs sm:grid-cols-4 sm:gap-4 ${
i !== arr.length - 1 ? "border-b border-white/[0.04]" : ""
}`}
>
<code className="font-mono text-indigo-300">{row.name}</code>
<span className="text-white/60">{row.type}</span>
<span className="text-white/40 sm:col-span-1">{row.purpose}</span>
<span className="text-white/40">{row.expires}</span>
</div>
{[
{ name: "pf_session", type: "Essential", purpose: "Authentication session token", expires: "7 days" },
{ name: "pf_prefs", type: "Preference", purpose: "Dashboard layout preferences", expires: "1 year" },
{ name: "_vercel_*", type: "Analytics", purpose: "Anonymous page performance data", expires: "90 days" },
{ name: "__stripe_*", type: "Third-party", purpose: "Stripe payment fraud prevention", expires: "Session" },
].map((row, i, arr) => (
<div key={row.name} className={`grid grid-cols-4 gap-4 px-5 py-3.5 text-xs ${i !== arr.length - 1 ? "border-b border-white/[0.04]" : ""}`}>
<code className="font-mono text-indigo-300">{row.name}</code>
<span className="text-white/60">{row.type}</span>
<span className="text-white/40 col-span-1">{row.purpose}</span>
<span className="text-white/40">{row.expires}</span>
</div>
))}
</div>
))}
</div>
</div>
</LegalPage>
)
}
+94
View File
@@ -0,0 +1,94 @@
import { LegalPage, Section, Callout, LegalContact } from "@/components/marketing/legal"
import { LEGAL } from "@/lib/legal"
export const metadata = {
title: "Disclaimer",
description:
"Important limitations on the information and outputs provided by the Service, including AI-generated content and financial reports.",
alternates: { canonical: "/disclaimer" },
}
export default function Page() {
return (
<LegalPage
title="Disclaimer"
subtitle="Please read these important limitations regarding the information and outputs provided by the Service."
>
<Callout>
<strong>{LEGAL.service}</strong> (the <strong>Service</strong>) and all of its outputs are
provided for general informational purposes only and do <strong>not</strong> constitute
professional advice. You should not rely on them as a substitute for advice from a qualified
professional.
</Callout>
<Section heading="General information only">
<p>
The information made available through the Service is provided by{" "}
<strong>{LEGAL.entity}</strong> (<strong>we</strong>, <strong>us</strong>, or{" "}
<strong>our</strong>) for general informational purposes. While we aim to keep the Service
useful and reliable, we make no representations or warranties as to the accuracy,
completeness, or timeliness of any information or output.
</p>
</Section>
<Section heading="No professional advice">
<p>
Nothing provided through the Service constitutes <strong>legal</strong>,{" "}
<strong>tax</strong>, <strong>financial</strong>, <strong>accounting</strong>, or{" "}
<strong>real-estate</strong> advice. You (the account holder, referred to as{" "}
<strong>you</strong> or the <strong>Customer</strong>) should consult qualified
professionals before making any decision based on the Service.
</p>
</Section>
<Section heading="AI-generated content">
<p>
Some features generate content using artificial intelligence. AI-generated content may be{" "}
<strong>inaccurate, incomplete, or outdated</strong>, and may not reflect your specific
circumstances. You must independently verify any AI-generated content before relying on it.
The <strong>Customer</strong> is solely responsible for any decision made using such
content.
</p>
</Section>
<Section heading="Financial figures and reports">
<p>
Any financial figures, summaries, and reports produced by the Service are provided for
convenience only. You should verify them against your own official records. These outputs
are <strong>not</strong> a substitute for professional accounting, bookkeeping, or audit
services.
</p>
</Section>
<Section heading="Legal and regulatory compliance">
<p>
The <strong>Customer</strong> is solely responsible for complying with all applicable laws
and regulations, including <strong>housing</strong>, <strong>fair-housing</strong>,{" "}
<strong>landlord-tenant</strong>, <strong>tax</strong>, and{" "}
<strong>data-protection</strong> laws. The Service is a tool to assist you and does not
ensure or guarantee your compliance with any legal obligation.
</p>
</Section>
<Section heading="Third-party content and links">
<p>
The Service may include content from, or links to, third-party websites and services. We do
not control and are not responsible for the content, accuracy, or practices of any third
party. The inclusion of any link or third-party content does not imply endorsement.
</p>
</Section>
<Section heading="No warranty and limitation of liability">
<p>
The Service is provided on an <strong>&ldquo;as is&rdquo;</strong> and{" "}
<strong>&ldquo;as available&rdquo;</strong> basis, without warranties of any kind, whether
express or implied, to the fullest extent permitted by law. This page is a summary only. The
full warranty disclaimer and the limitation of liability, including any liability cap, are
set out in our <a href="/terms">Terms of Service</a>, which govern your use of the Service.
</p>
</Section>
<LegalContact email={LEGAL.legalEmail} />
</LegalPage>
)
}
+247
View File
@@ -0,0 +1,247 @@
import Link from "next/link"
import { LegalPage, Section, Callout, LegalContact } from "@/components/marketing/legal"
import { LEGAL } from "@/lib/legal"
export const metadata = {
title: "Data Processing Addendum",
description: `The Data Processing Addendum governing how ${LEGAL.entity} processes personal data on behalf of Customers using ${LEGAL.service}.`,
alternates: { canonical: "/dpa" },
}
export default function DpaPage() {
return (
<LegalPage
title="Data Processing Addendum"
subtitle={`This Data Processing Addendum sets out the terms on which ${LEGAL.entity} processes personal data on behalf of Customers of ${LEGAL.service}.`}
>
<Callout>
This DPA forms part of the{" "}
<Link href="/terms">Terms of Service</Link> between you and {LEGAL.entity} and
applies wherever we process personal data on the Customer&rsquo;s behalf (for
example, Tenant personal data managed through the Service). Where there is a
conflict between this DPA and the Terms in respect of the processing of personal
data, this DPA prevails.
</Callout>
<Section id="roles" heading="1. Introduction and roles">
<p>
In this DPA, <strong>Customer</strong> (also <strong>you</strong>) means the
account holder using {LEGAL.service}. <strong>We</strong>, <strong>us</strong>,
and <strong>our</strong> mean {LEGAL.entity}. A <strong>Tenant</strong> means a
data subject whose personal data the Customer manages through the Service.
</p>
<p>
With respect to Tenant personal data and other personal data that the Customer
submits to the Service, the Customer acts as the data{" "}
<strong>controller</strong> and we act as the data{" "}
<strong>processor</strong>, processing that personal data solely on the
Customer&rsquo;s behalf. With respect to the Customer&rsquo;s own account data
(for example, the name and contact details of the account holder and billing
information), we act as a <strong>controller</strong> in our own right, as
described in our{" "}
<Link href="/privacy">Privacy Policy</Link>.
</p>
</Section>
<Section id="definitions" heading="2. Definitions">
<p>Unless otherwise defined here, the following terms have the meanings given below:</p>
<ul>
<li>
<strong>Controller</strong> means the entity that determines the purposes and
means of the processing of personal data.
</li>
<li>
<strong>Processor</strong> means the entity that processes personal data on
behalf of the controller.
</li>
<li>
<strong>Personal Data</strong> means any information relating to an identified
or identifiable natural person that is processed under this DPA.
</li>
<li>
<strong>Data Subject</strong> means the identified or identifiable natural
person to whom Personal Data relates.
</li>
<li>
<strong>Processing</strong> means any operation performed on Personal Data,
whether or not by automated means, including collection, storage, use, and
deletion.
</li>
<li>
<strong>Sub-processor</strong> means any third party engaged by us to process
Personal Data on behalf of the Customer.
</li>
<li>
<strong>Applicable Data Protection Law</strong> means all laws and regulations
applicable to the processing of Personal Data under this DPA, including the EU
General Data Protection Regulation (Regulation (EU) 2016/679) (the{" "}
<strong>GDPR</strong>) and the United Kingdom General Data Protection
Regulation (the <strong>UK GDPR</strong>).
</li>
<li>
<strong>Standard Contractual Clauses</strong> means the standard data
protection clauses approved by the European Commission (or the equivalent UK
transfer mechanism) for the transfer of Personal Data to processors
established in third countries.
</li>
</ul>
</Section>
<Section id="details" heading="3. Details of the processing">
<p>
The subject matter, duration, nature, and purpose of the processing, and the
types of Personal Data and categories of Data Subjects, are as follows:
</p>
<ul>
<li>
<strong>Subject matter:</strong> the provision of the Service to the Customer.
</li>
<li>
<strong>Duration:</strong> the term of the agreement between the Customer and
us, plus the deletion window described in Section 11.
</li>
<li>
<strong>Nature and purpose:</strong> hosting, storage, and processing of
Personal Data as necessary to operate the property-management features of the
Service.
</li>
<li>
<strong>Types of Personal Data:</strong> names, contact details, tenancy
information, lease information, and payment-status data.
</li>
<li>
<strong>Categories of Data Subjects:</strong> the Customer&rsquo;s Tenants and
contacts.
</li>
</ul>
</Section>
<Section id="obligations" heading="4. Our obligations as processor">
<p>When acting as a processor on the Customer&rsquo;s behalf, we shall:</p>
<ul>
<li>
process Personal Data only on the Customer&rsquo;s documented instructions,
including with regard to international transfers, unless required to do
otherwise by law (in which case we shall inform the Customer of that legal
requirement before processing, unless prohibited from doing so);
</li>
<li>
ensure that persons authorized to process Personal Data have committed
themselves to confidentiality or are under an appropriate statutory obligation
of confidentiality;
</li>
<li>
implement appropriate technical and organizational measures to ensure a level
of security appropriate to the risk, in accordance with Article 32 of the
GDPR;
</li>
<li>
taking into account the nature of the processing, assist the Customer by
appropriate technical and organizational measures in responding to requests
from Data Subjects seeking to exercise their rights;
</li>
<li>
assist the Customer in ensuring compliance with its obligations relating to
the security of processing, personal-data breach notification, data-protection
impact assessments (DPIAs), and prior consultations with supervisory
authorities;
</li>
<li>
make available to the Customer the information necessary to demonstrate
compliance with the obligations set out in this DPA.
</li>
</ul>
</Section>
<Section id="subprocessors" heading="5. Sub-processors">
<p>
The Customer provides a general authorization for us to engage Sub-processors to
process Personal Data in connection with the Service. Our current Sub-processors
are listed on our{" "}
<Link href="/subprocessors">Sub-processors</Link> page.
</p>
<p>
Where we engage a Sub-processor, we impose data-protection obligations that are
substantially equivalent to those set out in this DPA. We give the Customer prior
notice of any intended addition or replacement of a Sub-processor, and the
Customer may object to the change on legitimate data-protection grounds. We remain
responsible for the performance of each Sub-processor&rsquo;s obligations.
</p>
</Section>
<Section id="transfers" heading="6. International transfers">
<p>
Where processing of Personal Data involves a transfer to a country outside the
European Economic Area or the United Kingdom that has not been recognized as
providing an adequate level of protection, we implement an appropriate transfer
mechanism, such as the Standard Contractual Clauses or another lawful mechanism
recognized under Applicable Data Protection Law.
</p>
</Section>
<Section id="rights" heading="7. Data-subject rights">
<p>
Taking into account the nature of the processing, we assist the Customer, as
controller, by appropriate technical and organizational measures, insofar as
this is possible, in fulfilling the Customer&rsquo;s obligation to respond to
requests from Data Subjects exercising their rights under Applicable Data
Protection Law. Where we receive a request directly from a Data Subject in
respect of Personal Data processed on the Customer&rsquo;s behalf, we shall,
unless legally required to respond, forward that request to the Customer without
undue delay.
</p>
</Section>
<Section id="breach" heading="8. Personal-data breach">
<p>
We shall notify the Customer without undue delay after becoming aware of a
personal-data breach affecting Personal Data processed on the Customer&rsquo;s
behalf. That notification shall, to the extent available, describe the nature of
the breach, its likely consequences, and the measures taken or proposed to
address it, so that the Customer can meet its own notification obligations.
</p>
</Section>
<Section id="audit" heading="9. Audit">
<p>
We make available to the Customer the information necessary to demonstrate
compliance with this DPA and allow for and contribute to audits, including
inspections, conducted by the Customer or an auditor mandated by the Customer.
Audits are subject to reasonable prior written notice, are conducted during
normal business hours in a manner that does not disrupt our operations, and are
subject to appropriate confidentiality obligations.
</p>
</Section>
<Section id="deletion" heading="10. Return and deletion">
<p>
Upon termination or expiry of the agreement, we shall, at the Customer&rsquo;s
choice, delete or return all Personal Data processed on the Customer&rsquo;s
behalf, and delete existing copies, within {LEGAL.dataDeletionDays} days, save
where retention of the Personal Data is required by Applicable Data Protection
Law or other law, in which case we shall protect that Personal Data and process
it only as necessary for the purpose that requires its retention.
</p>
</Section>
<Section id="liability" heading="11. Liability">
<p>
Each party&rsquo;s liability under or in connection with this DPA is subject to
the exclusions and limitations of liability set out in the{" "}
<Link href="/terms">Terms of Service</Link>.
</p>
</Section>
<Section id="execution" heading="12. Execution">
<p>
This DPA is incorporated into, and forms part of, the Terms of Service and takes
effect upon the Customer&rsquo;s acceptance of the Terms and use of the Service.
A countersigned copy of this DPA is available on request by contacting{" "}
<a href={`mailto:${LEGAL.dpoEmail}`}>{LEGAL.dpoEmail}</a>.
</p>
</Section>
<LegalContact email={LEGAL.dpoEmail} />
</LegalPage>
)
}
+144 -127
View File
@@ -1,145 +1,162 @@
import Link from "next/link"
import { LegalPage, Section, LegalContact } from "@/components/marketing/legal"
import { LEGAL } from "@/lib/legal"
export const metadata = {
title: "GDPR Compliance — Property Management Network",
description: "Property Management Network's commitment to GDPR compliance and your data rights as a data subject.",
title: "GDPR & Data Rights",
description: `How ${LEGAL.entity} complies with the GDPR and UK GDPR, and the data rights available to you as a data subject.`,
alternates: { canonical: "/gdpr" },
}
const RIGHTS = [
{ right: "Right of access", desc: "You can request a full export of all data we hold about you at any time from your account settings." },
{ right: "Right to rectification", desc: "You can update your personal information directly in your account settings, or contact us to correct inaccurate data." },
{ right: "Right to erasure", desc: "You can permanently delete your account and all associated data from the settings page. Deletion is irreversible and processed within 30 days." },
{ right: "Right to data portability", desc: "You can export your data in machine-readable JSON or CSV format from the dashboard at any time." },
{ right: "Right to restriction", desc: "You may request that we restrict processing of your data while a dispute is being resolved." },
{ right: "Right to object", desc: "You may object to processing where we rely on legitimate interest. You can opt out of analytics tracking by enabling Do Not Track in your browser." },
]
export default function GdprPage() {
return (
<div className="bg-[#09090b] text-white min-h-screen">
<div className="mx-auto max-w-3xl px-6 pt-32 pb-24">
<div className="mb-10">
<div className="inline-flex items-center gap-2 rounded-full border border-emerald-500/30 bg-emerald-500/10 px-3 py-1 text-xs font-medium text-emerald-300 mb-4">
EU GDPR Compliant
</div>
<h1 className="text-3xl font-bold text-white mb-2">GDPR Compliance</h1>
<p className="text-xs text-white/30">Last updated: April 2026</p>
</div>
<LegalPage
title="GDPR & Data Rights"
subtitle={`Our commitment to the General Data Protection Regulation and the UK GDPR, and the rights available to you when ${LEGAL.entity} processes your personal data.`}
>
<Section id="introduction" heading="1. Introduction">
<p>
{LEGAL.entity} is committed to protecting personal data and to complying with the
General Data Protection Regulation (Regulation (EU) 2016/679) (the{" "}
<strong>GDPR</strong>) and the United Kingdom General Data Protection Regulation
(the <strong>UK GDPR</strong>) where applicable. This page explains the rights
available to individuals whose personal data we process and how those rights may
be exercised in connection with {LEGAL.service}.
</p>
</Section>
<div className="space-y-8 text-sm text-white/50 leading-relaxed">
<div>
<h2 className="text-base font-semibold text-white mb-2">Who we are</h2>
<p>
Property Management Network (&quot;we&quot;, &quot;us&quot;, &quot;our&quot;) is the data controller for personal data collected through our platform.
We are committed to complying with the General Data Protection Regulation (EU) 2016/679 (GDPR)
and the UK GDPR where applicable.
</p>
</div>
<Section id="rights" heading="2. Your rights under the GDPR and UK GDPR">
<p>
Subject to the conditions in Applicable Data Protection Law, you have the
following rights:
</p>
<ul>
<li>
<strong>Right of access</strong> &mdash; to obtain confirmation of whether we
process your personal data and to receive a copy of it.
</li>
<li>
<strong>Right to rectification</strong> &mdash; to have inaccurate personal
data corrected and incomplete data completed.
</li>
<li>
<strong>Right to erasure</strong> &mdash; to have your personal data deleted in
certain circumstances.
</li>
<li>
<strong>Right to restriction of processing</strong> &mdash; to limit how we
process your personal data in certain circumstances.
</li>
<li>
<strong>Right to data portability</strong> &mdash; to receive your personal
data in a structured, commonly used, machine-readable format.
</li>
<li>
<strong>Right to object</strong> &mdash; to object to processing that relies on
our legitimate interests.
</li>
<li>
<strong>Right to withdraw consent</strong> &mdash; where processing is based on
consent, to withdraw that consent at any time.
</li>
<li>
<strong>Right to lodge a complaint</strong> &mdash; to lodge a complaint with a
supervisory authority.
</li>
</ul>
</Section>
<div>
<h2 className="text-base font-semibold text-white mb-2">What data we process</h2>
<p className="mb-3">We process the following categories of personal data:</p>
<ul className="space-y-2">
{[
"Account data: name, email address, password hash",
"Property data: addresses, rental amounts, lease terms you enter",
"Tenant data: names, emails, phone numbers you provide as a landlord",
"Payment data: payment amounts, dates, and status (card details handled by Stripe, not us)",
"Usage data: pages visited, features used — anonymised via Vercel Analytics",
].map((item) => (
<li key={item} className="flex items-start gap-2">
<span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-400" />
{item}
</li>
))}
</ul>
</div>
<Section id="legal-bases" heading="3. Legal bases for processing">
<p>
We process personal data on one or more of the following legal bases, depending
on the context:
</p>
<ul>
<li>
<strong>Contract</strong> &mdash; where processing is necessary to provide the
Service you have requested.
</li>
<li>
<strong>Legitimate interests</strong> &mdash; where processing is necessary for
our legitimate interests, such as securing and improving the Service, provided
those interests are not overridden by your rights.
</li>
<li>
<strong>Consent</strong> &mdash; where you have given consent for a specific
purpose.
</li>
<li>
<strong>Legal obligation</strong> &mdash; where processing is necessary to
comply with a legal obligation to which we are subject.
</li>
</ul>
</Section>
<div>
<h2 className="text-base font-semibold text-white mb-2">Legal basis for processing</h2>
<p>
We process personal data on the following legal bases: (1) Contract data necessary to provide the service you signed up for.
(2) Legitimate interest anonymous analytics to improve the product. (3) Legal obligation where required by applicable law.
We do not process data on the basis of consent for core functionality.
</p>
</div>
<Section id="storage" heading="4. Data storage and international transfers">
<p>
Personal data is stored in a managed PostgreSQL database hosted on DigitalOcean,
with an EU region available. Uploaded files are stored privately in DigitalOcean
Spaces. Access isolation is enforced at the application layer: every request is
authenticated and scoped to the relevant account so that data is not accessible
to other users. Where personal data is transferred to a country that has not been
recognized as providing an adequate level of protection, the transfer is protected
by Standard Contractual Clauses or another lawful transfer mechanism.
</p>
</Section>
<div>
<h2 className="text-base font-semibold text-white mb-2">Data storage and transfers</h2>
<p>
Your data is stored in Supabase (PostgreSQL), with servers located in the EU (Frankfurt, Germany) by default.
Row-level security (RLS) policies ensure only you can access your data. We do not transfer personal data outside
the EEA except where strictly necessary for integrated services (e.g. Stripe for payment processing,
which is covered by Standard Contractual Clauses).
</p>
</div>
<Section id="retention" heading="5. Data retention">
<p>
We retain personal data for as long as it is needed to provide the Service. Upon
deletion of an account, associated personal data is deleted within{" "}
{LEGAL.dataDeletionDays} days, except where a longer retention period is required
by law (for example, certain financial records that must be kept for tax
purposes).
</p>
</Section>
<div>
<h2 className="text-base font-semibold text-white mb-2">Your rights under GDPR</h2>
<p className="mb-5">As a data subject, you have the following rights:</p>
<div className="space-y-4">
{RIGHTS.map((r) => (
<div key={r.right} className="rounded-xl border border-white/[0.06] bg-[#111118] p-4">
<p className="text-sm font-semibold text-white mb-1">{r.right}</p>
<p className="text-xs text-white/50 leading-relaxed">{r.desc}</p>
</div>
))}
</div>
</div>
<Section id="breach" heading="6. Personal-data breach notification">
<p>
In the event of a personal-data breach, we notify affected users and, where
required, the relevant supervisory authority within 72 hours of becoming aware of
the breach, consistent with Article 33 of the GDPR.
</p>
</Section>
<div>
<h2 className="text-base font-semibold text-white mb-2">Data retention</h2>
<p>
We retain account data for as long as your account is active. Upon deletion, all personal data is purged within 30 days,
except where retention is required by law (e.g. financial records may be retained for up to 7 years for tax compliance).
</p>
</div>
<Section id="subprocessors" heading="7. Sub-processors">
<p>
We use vetted third-party sub-processors to help operate the Service. Our current
sub-processors are listed on our{" "}
<Link href="/subprocessors">Sub-processors</Link> page, and the terms governing
their engagement are set out in our{" "}
<Link href="/dpa">Data Processing Addendum</Link>.
</p>
</Section>
<div>
<h2 className="text-base font-semibold text-white mb-2">Data breach notification</h2>
<p>
In the event of a data breach affecting your personal data, we will notify affected users within 72 hours of becoming aware,
in accordance with GDPR Article 33 obligations.
</p>
</div>
<Section id="exercise" heading="8. How to exercise your rights">
<p>
To exercise any of the rights described above, contact us at{" "}
<a href={`mailto:${LEGAL.privacyEmail}`}>{LEGAL.privacyEmail}</a>. For
data-protection matters, you may also contact our data-protection team at{" "}
<a href={`mailto:${LEGAL.dpoEmail}`}>{LEGAL.dpoEmail}</a>. You also have the right
to lodge a complaint with your local supervisory authority (for example, the
Information Commissioner&rsquo;s Office in the United Kingdom, or your national
data-protection authority in the European Union).
</p>
</Section>
<div>
<h2 className="text-base font-semibold text-white mb-2">Sub-processors</h2>
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] overflow-hidden">
{[
{ name: "Supabase", purpose: "Database & file storage", location: "EU (Frankfurt)" },
{ name: "Stripe", purpose: "Payment processing", location: "US (SCCs in place)" },
{ name: "Resend", purpose: "Transactional email", location: "US (SCCs in place)" },
{ name: "Vercel", purpose: "Hosting & edge network", location: "Global (anonymised data only)" },
].map((sp, i, arr) => (
<div key={sp.name} className={`grid grid-cols-3 gap-4 px-5 py-3.5 text-xs ${i !== arr.length - 1 ? "border-b border-white/[0.04]" : ""}`}>
<span className="font-semibold text-white">{sp.name}</span>
<span className="text-white/50">{sp.purpose}</span>
<span className="text-white/40">{sp.location}</span>
</div>
))}
</div>
</div>
<Section id="roles" heading="9. Roles: controller versus processor">
<p>
Where you use the Service to manage the personal data of your Tenants, you act as
the data <strong>controller</strong> and we act as the data{" "}
<strong>processor</strong>, processing that personal data on your documented
instructions under our{" "}
<Link href="/dpa">Data Processing Addendum</Link>. Where we process your own
account data, we act as a <strong>controller</strong>, as described in our{" "}
<Link href="/privacy">Privacy Policy</Link>.
</p>
</Section>
<div>
<h2 className="text-base font-semibold text-white mb-2">Contact & complaints</h2>
<p>
To exercise any of your rights or to raise a data protection concern, contact our Data Protection lead at{" "}
<a href="mailto:privacy@propertymanagement.network" className="text-indigo-400 hover:text-indigo-300">
privacy@propertymanagement.network
</a>
. You also have the right to lodge a complaint with your local supervisory authority (e.g. the ICO in the UK,
or your national DPA in the EU).
</p>
<p className="mt-4">
See also our{" "}
<Link href="/privacy" className="text-indigo-400 hover:text-indigo-300">Privacy Policy</Link>{" "}
and{" "}
<Link href="/cookie-policy" className="text-indigo-400 hover:text-indigo-300">Cookie Policy</Link>.
</p>
</div>
</div>
</div>
</div>
<LegalContact email={LEGAL.dpoEmail} />
</LegalPage>
)
}
+15 -1
View File
@@ -1,9 +1,23 @@
import { Navbar } from "@/components/marketing/navbar"
import { Footer } from "@/components/marketing/footer"
import { StructuredData } from "@/components/marketing/structured-data"
import { getSession, isAdminUser } from "@/lib/session"
import { getMaintenanceMode } from "@/lib/settings"
import { MaintenanceScreen } from "@/components/shared/maintenance-screen"
export default async function MarketingLayout({ children }: { children: React.ReactNode }) {
// Site maintenance mode: show the maintenance screen to everyone except admins.
const maintenance = await getMaintenanceMode()
if (maintenance.enabled) {
const session = await getSession()
if (!isAdminUser(session?.user)) {
return <MaintenanceScreen message={maintenance.message} />
}
}
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-[#09090b] text-white">
<StructuredData />
<Navbar />
{children}
<Footer />
+11 -3
View File
@@ -7,10 +7,18 @@ import { Testimonials } from "@/components/marketing/testimonials"
import { PricingSection } from "@/components/marketing/pricing-section"
import { FAQ } from "@/components/marketing/faq"
import { CtaBanner } from "@/components/marketing/cta-banner"
import { annualEnabled } from "@/lib/stripe/plans"
export const metadata = {
title: "Property Management Network — Property management without the chaos",
description: "Track rent, manage maintenance, monitor leases, and keep expenses organised. Built for independent landlords.",
title: { absolute: "Property Management Software for Independent Landlords" },
description: "Track rent, manage maintenance, monitor leases, and keep expenses organised — all in one dashboard built for independent landlords. Free to start.",
alternates: { canonical: "/" },
openGraph: {
title: "Property management without the chaos",
description: "Track rent, manage maintenance, monitor leases, and keep expenses organised — all in one dashboard built for independent landlords. Free to start.",
url: "/",
type: "website",
},
}
export default function LandingPage() {
@@ -22,7 +30,7 @@ export default function LandingPage() {
<Features />
<HowItWorks />
<Testimonials />
<PricingSection />
<PricingSection annualEnabled={annualEnabled()} />
<FAQ />
<CtaBanner />
</>
+250 -13
View File
@@ -1,17 +1,254 @@
export const metadata = { title: "Privacy Policy — Property Management Network" }
import { LegalPage, Section, Callout, LegalContact } from "@/components/marketing/legal"
import { LEGAL, SUBPROCESSORS } from "@/lib/legal"
export default function PrivacyPage() {
export const metadata = {
title: "Privacy Policy",
description:
"How we collect, use, share, and protect personal data, and the privacy rights available to you under the GDPR and California law.",
alternates: { canonical: "/privacy" },
}
export default function Page() {
return (
<div className="mx-auto max-w-3xl px-6 py-24">
<h1 className="text-3xl font-bold text-white mb-8">Privacy Policy</h1>
<p className="text-white/50 text-sm leading-relaxed">
This Privacy Policy describes how Property Management Network collects, uses, and protects your information.
We collect only the data necessary to provide the service (account info, property data you enter,
and usage analytics). Your data is stored securely in Supabase with row-level security
no other user can access your records. We do not sell your data to third parties.
Files you upload are stored in private buckets and accessible only to you.
For questions, contact us at support@propertymanagement.network.
</p>
</div>
<LegalPage
title="Privacy Policy"
subtitle="This policy explains what personal data we process, why, how we protect it, and the rights you may exercise."
>
<Section heading="Introduction and scope">
<p>
<strong>{LEGAL.service}</strong> (the <strong>Service</strong>) is operated by{" "}
<strong>{LEGAL.entity}</strong> (<strong>we</strong>, <strong>us</strong>, or{" "}
<strong>our</strong>). This Privacy Policy describes how we handle personal data when you
(the account holder, referred to as <strong>you</strong> or the{" "}
<strong>Customer</strong>&mdash;typically a landlord or property manager) use the Service,
and how we handle personal data relating to a <strong>Tenant</strong>, meaning an end user
whose data the Customer manages within the Service.
</p>
<p>
We act in two distinct roles. We are the <strong>controller</strong> of the account and
profile data that relates to your use of the Service. We are a <strong>processor</strong>{" "}
of the property, unit, Tenant, lease, and financial records that the Customer enters,
because the Customer determines the purposes and means of that processing. Our processing
of that data on the Customer&rsquo;s behalf is governed by our{" "}
<a href="/dpa">Data Processing Addendum</a>.
</p>
</Section>
<Section heading="Information we collect">
<p>We collect the following categories of information:</p>
<ul>
<li>
<strong>Account and profile data</strong>&mdash;such as your name, email address,
password credentials, organization details, and preferences.
</li>
<li>
<strong>Portfolio data you enter</strong>&mdash;property, unit, Tenant, lease, rent,
maintenance, and expense records that the Customer creates or uploads to the Service.
</li>
<li>
<strong>Payment metadata</strong>&mdash;billing information processed through Stripe.
Stripe handles card details directly; we never receive or store full card numbers.
</li>
<li>
<strong>Usage and device or log data</strong>&mdash;such as IP address, browser type,
device information, pages accessed, and timestamps generated when you use the Service.
</li>
<li>
<strong>Cookies and similar technologies</strong>&mdash;as described in our{" "}
<a href="/cookie-policy">Cookie Policy</a>.
</li>
</ul>
</Section>
<Section heading="How we use information">
<p>We use personal data to:</p>
<ul>
<li>Provide, operate, maintain, and improve the Service;</li>
<li>Process billing, subscriptions, and payments through Stripe;</li>
<li>Protect the Service through security monitoring and fraud prevention;</li>
<li>Respond to support requests and communicate with you about your account;</li>
<li>
Generate optional AI insights when you choose to use AI features (described below); and
</li>
<li>Comply with legal obligations and enforce our agreements.</li>
</ul>
</Section>
<Section heading="Legal bases for processing">
<p>
Where the General Data Protection Regulation (GDPR) applies, we rely on the following legal
bases:
</p>
<ul>
<li>
<strong>Performance of a contract</strong>&mdash;to provide the Service you have signed
up for and to administer your account and billing.
</li>
<li>
<strong>Legitimate interests</strong>&mdash;to secure, maintain, and improve the Service
and to prevent fraud and abuse, provided such interests are not overridden by your
rights.
</li>
<li>
<strong>Consent</strong>&mdash;where you have given it, for example when you choose to use
optional features such as AI insights or optional sign-in providers.
</li>
<li>
<strong>Legal obligation</strong>&mdash;to comply with applicable laws, including the
retention of certain financial records.
</li>
</ul>
</Section>
<Section heading="AI processing">
<p>
When you choose to use AI features, the relevant portfolio data is sent to{" "}
<strong>OpenAI</strong> on a per-request basis in order to generate the requested insight.
That data is <strong>not used to train models</strong>. AI features are optional: if you do
not use them, no portfolio data is transmitted to OpenAI. See our{" "}
<a href="/subprocessors">sub-processors</a> page for further detail.
</p>
</Section>
<Section heading="How we share information">
<p>We share personal data only in the limited circumstances described below.</p>
<ul>
<li>
<strong>Service providers and sub-processors</strong>&mdash;we engage trusted vendors to
host, operate, and support the Service. Our current sub-processors include{" "}
{SUBPROCESSORS.map((sp, i) => (
<span key={sp.name}>
<strong>{sp.name}</strong>
{i < SUBPROCESSORS.length - 1 ? ", " : ""}
</span>
))}
. The full, current list is maintained on our{" "}
<a href="/subprocessors">sub-processors</a> page.
</li>
<li>
<strong>Legal and safety</strong>&mdash;where required to comply with law, respond to
lawful requests, or protect the rights, property, or safety of any person.
</li>
<li>
<strong>Business transfers</strong>&mdash;in connection with a merger, acquisition,
financing, or sale of assets, subject to this policy.
</li>
</ul>
<p>
We do <strong>not</strong> sell or rent personal data.
</p>
</Section>
<Section heading="International data transfers">
<p>
Personal data may be processed in countries other than the one in which it was collected,
including the United States. Where we transfer personal data across borders, we rely on
appropriate safeguards such as the <strong>Standard Contractual Clauses</strong> approved by
the European Commission, together with supplementary measures where required.
</p>
</Section>
<Section heading="Data retention">
<p>
We retain personal data for as long as your account remains active. Following account
deletion, we delete or anonymize personal data within{" "}
<strong>{LEGAL.dataDeletionDays} days</strong>, except where a longer retention period is
required by law or for legitimate business purposes such as the retention of financial and
tax records.
</p>
</Section>
<Section heading="Security">
<p>
We protect personal data using encryption in transit and at rest, and we enforce access
isolation at the <strong>application layer</strong>: every request is authenticated and
scoped to the relevant account, so that one account cannot access another account&rsquo;s
records. Uploaded files are stored privately and served only through authenticated,
per-account access. We also employ authentication controls and automated bot protection on
our sign-in forms. No method of transmission or storage is <strong>100% secure</strong>, and
we cannot guarantee absolute security.
</p>
</Section>
<Section heading="Your privacy rights">
<p>
Depending on where you live, you may have the following rights in relation to your personal
data.
</p>
<p>
<strong>Rights under the GDPR.</strong> If you are in the European Economic Area or the
United Kingdom, you may request:
</p>
<ul>
<li>Access to your personal data;</li>
<li>Rectification of inaccurate or incomplete data;</li>
<li>Erasure of your data;</li>
<li>Restriction of processing;</li>
<li>Portability of the data you have provided;</li>
<li>To object to certain processing;</li>
<li>To withdraw consent where processing is based on consent; and</li>
<li>To lodge a complaint with a supervisory authority.</li>
</ul>
<p>
<strong>Rights under California law (CCPA and CPRA).</strong> If you are a California
resident, you may request to know, delete, and correct the personal information we hold
about you, and to opt out of any sale or sharing of personal information. We do{" "}
<strong>not</strong> sell or share personal information for cross-context behavioral
advertising.
</p>
<p>
To exercise any of these rights, contact us at{" "}
<a href={`mailto:${LEGAL.privacyEmail}`}>{LEGAL.privacyEmail}</a>. We will respond within
the timeframe required by applicable law and may need to verify your identity before acting
on your request. For further detail on data rights, see our{" "}
<a href="/gdpr">GDPR and Data Rights</a> page.
</p>
</Section>
<Section heading="Notice to Tenants">
<p>
If you are a <strong>Tenant</strong>, your landlord or property manager (the{" "}
<strong>Customer</strong>) is the <strong>controller</strong> of the data held about you
within the Service, and we act as a <strong>processor</strong> on their behalf. Please
direct requests to access, correct, or delete your data to the Customer who manages your
tenancy. We will assist that Customer in responding, as set out in our{" "}
<a href="/dpa">Data Processing Addendum</a>.
</p>
</Section>
<Section heading="Children">
<p>
The Service is not directed to children, and we do not knowingly collect personal data from
children. If you believe a child has provided us with personal data, please contact us so
that we can delete it.
</p>
</Section>
<Section heading="Changes to this policy">
<p>
We may update this Privacy Policy from time to time. When we make material changes, we will
update the date shown above and, where appropriate, provide additional notice. Your
continued use of the Service after an update constitutes acceptance of the revised policy.
</p>
</Section>
<Callout>
This Privacy Policy should be read together with our <a href="/terms">Terms of Service</a>,{" "}
<a href="/cookie-policy">Cookie Policy</a>, and{" "}
<a href="/dpa">Data Processing Addendum</a>.
</Callout>
<LegalContact email={LEGAL.privacyEmail}>
{
"If you have questions about this policy or wish to exercise your privacy rights, contact us at "
}
</LegalContact>
<Section heading="Data protection contact">
<p>
For data-protection matters specifically, you may also contact our data protection team at{" "}
<a href={`mailto:${LEGAL.dpoEmail}`}>{LEGAL.dpoEmail}</a>.
</p>
</Section>
</LegalPage>
)
}
+115
View File
@@ -0,0 +1,115 @@
import { LegalPage, Section, Callout, LegalContact } from "@/components/marketing/legal"
import { LEGAL } from "@/lib/legal"
export const metadata = {
title: "Refund & Cancellation Policy",
description: `How subscriptions, cancellations, and refunds work for the ${LEGAL.service} platform.`,
alternates: { canonical: "/refund-policy" },
}
export default function Page() {
return (
<LegalPage
title="Refund & Cancellation Policy"
subtitle={`This policy explains how billing, cancellations, and refunds work for the ${LEGAL.service} platform. It forms part of our Terms of Service.`}
>
<Section heading="1. Overview">
<p>
This Refund &amp; Cancellation Policy (the <strong>Policy</strong>) describes how subscriptions to the{" "}
{LEGAL.service} platform (the <strong>Service</strong>), operated by {LEGAL.entity} (<strong>we</strong>,{" "}
<strong>us</strong>, or <strong>our</strong>), are billed, how you (the <strong>Customer</strong>) may
cancel, and when refunds are available. This Policy forms part of our <a href="/terms">Terms of
Service</a>. In the event of any conflict, the Terms of Service govern.
</p>
</Section>
<Section heading="2. Subscription billing">
<p>
The Service is offered under the <strong>Starter</strong> (free), <strong>Pro</strong> ($29 per month),{" "}
<strong>Landlord</strong> ($59 per month), and <strong>Lifetime</strong> ($199 one-time) plans. Monthly
recurring plans are billed in advance at the start of each billing cycle through our payment processor,{" "}
<strong>Stripe</strong>, and renew automatically until cancelled.
</p>
</Section>
<Section heading="3. Cancellation">
<p>
You may cancel a paid subscription at any time from your billing settings or through the Stripe customer
portal. When you cancel a monthly plan, your subscription remains active and you retain access until the end
of the billing period you have already paid for; it will not renew for the following period.
</p>
<p>
Because monthly plans are billed in advance, <strong>we do not provide partial-period refunds for the
unused portion of a monthly billing cycle</strong> unless required by applicable law or expressly granted by
us.
</p>
</Section>
<Section heading="4. Refunds">
<p>
Except as expressly stated in this Policy or required by applicable law, monthly subscription fees are
generally <strong>non-refundable</strong>. We may, at our sole discretion, grant a refund or credit in
individual cases, for example in the event of an extended service outage or a billing error.
</p>
<p>
To request a refund or raise a billing concern, contact us at{" "}
<a href={`mailto:${LEGAL.contactEmail}`}>{LEGAL.contactEmail}</a> with your account details and a
description of the issue, and we will review your request.
</p>
</Section>
<Section heading="5. Lifetime plan">
<p>
The <strong>Lifetime</strong> plan is a single, one-time charge rather than a recurring subscription. The
Lifetime plan is refundable if you request a refund within {LEGAL.lifetimeRefundDays} days of the date of
purchase. After the {LEGAL.lifetimeRefundDays}-day window has passed, the Lifetime plan is{" "}
<strong>non-refundable</strong>, except where a refund is required by applicable law.
</p>
</Section>
<Section heading="6. Failed payments and past-due accounts">
<p>
If a scheduled payment fails, we and Stripe may attempt to charge your payment method again over a short
period (a process known as dunning). If payment remains unsuccessful and your account becomes past due, we
may downgrade your account to the free plan, suspend paid features, or ultimately suspend access to the
Service until the outstanding amount is paid.
</p>
</Section>
<Section heading="7. Price changes">
<p>
We may change the fees for a plan from time to time. If a price change affects your subscription, we will
provide advance notice, and the new price will take effect on your next billing cycle. If you do not wish to
accept a price change, you may cancel before it takes effect.
</p>
</Section>
<Section heading="8. Chargebacks">
<p>
If you believe you have been charged in error, please contact us at{" "}
<a href={`mailto:${LEGAL.contactEmail}`}>{LEGAL.contactEmail}</a> before initiating a chargeback with your
bank or card issuer, so that we can resolve the matter quickly. Initiating a chargeback or payment dispute
without first contacting us may lead to suspension or termination of your account while the dispute is
investigated.
</p>
</Section>
<Section heading="9. Statutory consumer rights">
<p>
If you are a consumer in the European Union, the United Kingdom, or another jurisdiction that grants a
statutory right of withdrawal or cancellation, you may have rights that are additional to those described in
this Policy. <strong>Nothing in this Policy limits or overrides any non-waivable statutory rights you may
have as a consumer under applicable law.</strong>
</p>
</Section>
<Callout>
<strong>In short:</strong> you may cancel a monthly plan at any time and keep access until the end of the
period you have paid for, monthly fees are otherwise non-refundable, and the Lifetime plan is refundable
within {LEGAL.lifetimeRefundDays} days of purchase.
</Callout>
<LegalContact email={LEGAL.legalEmail} />
</LegalPage>
)
}
-118
View File
@@ -1,118 +0,0 @@
export const metadata = {
title: "System Status — Property Management Network",
description: "Real-time status of all Property Management Network services.",
}
const SERVICES = [
{ name: "Web Application", status: "operational", uptime: "99.98%" },
{ name: "API Gateway", status: "operational", uptime: "99.97%" },
{ name: "Database (Supabase)", status: "operational", uptime: "99.99%" },
{ name: "Payment Processing (Stripe)", status: "operational", uptime: "99.95%" },
{ name: "Email Delivery (Resend)", status: "operational", uptime: "99.96%" },
{ name: "File Storage", status: "operational", uptime: "99.99%" },
{ name: "Tenant Portal", status: "operational", uptime: "99.97%" },
{ name: "Webhook Delivery", status: "operational", uptime: "99.90%" },
]
const INCIDENTS = [
{
date: "2026-03-28",
title: "Resolved: Delayed email notifications",
detail: "Email notifications were delayed by up to 12 minutes due to a Resend upstream issue. Fully resolved at 14:32 UTC.",
severity: "minor",
},
{
date: "2026-02-14",
title: "Resolved: Slow dashboard load times",
detail: "Database query optimisations were deployed to fix a slow index scan affecting dashboards with 50+ units. Resolved in 45 minutes.",
severity: "minor",
},
]
export default function StatusPage() {
const allOperational = SERVICES.every((s) => s.status === "operational")
return (
<div className="bg-[#09090b] text-white min-h-screen">
<div className="mx-auto max-w-3xl px-6 pt-32 pb-24">
{/* Header */}
<div className="mb-10">
<h1 className="text-3xl font-bold text-white mb-2">System Status</h1>
<p className="text-white/40 text-sm">Real-time health of all Property Management Network services.</p>
</div>
{/* Overall status */}
<div className={`flex items-center gap-3 rounded-2xl border p-5 mb-8 ${allOperational ? "border-emerald-500/20 bg-emerald-500/5" : "border-red-500/20 bg-red-500/5"}`}>
<div className={`relative flex h-3 w-3`}>
<span className={`animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 ${allOperational ? "bg-emerald-400" : "bg-red-400"}`} />
<span className={`relative inline-flex rounded-full h-3 w-3 ${allOperational ? "bg-emerald-400" : "bg-red-400"}`} />
</div>
<div>
<p className={`font-semibold text-sm ${allOperational ? "text-emerald-300" : "text-red-300"}`}>
{allOperational ? "All systems operational" : "Partial outage detected"}
</p>
<p className="text-xs text-white/30 mt-0.5">Last checked: just now</p>
</div>
</div>
{/* Services */}
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] overflow-hidden mb-10">
<div className="px-5 py-3.5 border-b border-white/[0.04]">
<p className="text-xs font-semibold uppercase tracking-wider text-white/30">Services</p>
</div>
{SERVICES.map((svc, i) => (
<div key={svc.name} className={`flex items-center justify-between px-4 sm:px-5 py-3.5 sm:py-4 gap-3 ${i !== SERVICES.length - 1 ? "border-b border-white/[0.04]" : ""}`}>
<div className="flex items-center gap-2 min-w-0">
<div className={`shrink-0 h-2 w-2 rounded-full ${svc.status === "operational" ? "bg-emerald-400" : svc.status === "degraded" ? "bg-amber-400" : "bg-red-400"}`} />
<span className="text-sm text-white/80 truncate">{svc.name}</span>
</div>
<div className="flex items-center gap-3 sm:gap-6 shrink-0">
<span className="hidden sm:block text-xs text-white/30 tabular-nums">{svc.uptime} uptime</span>
<span className={`text-xs font-medium capitalize ${svc.status === "operational" ? "text-emerald-400" : svc.status === "degraded" ? "text-amber-400" : "text-red-400"}`}>
{svc.status}
</span>
</div>
</div>
))}
</div>
{/* Uptime graph placeholder */}
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] p-5 mb-10">
<p className="text-xs font-semibold uppercase tracking-wider text-white/30 mb-4">90-day uptime</p>
<div className="flex gap-px sm:gap-0.5 h-6 sm:h-8 items-end">
{Array.from({ length: 90 }).map((_, i) => (
<div
key={i}
className="flex-1 rounded-sm bg-emerald-500/70"
style={{ height: `${Math.random() > 0.03 ? 100 : Math.floor(Math.random() * 60 + 20)}%` }}
/>
))}
</div>
<div className="flex justify-between mt-2 text-[10px] text-white/20">
<span>90 days ago</span>
<span>Today</span>
</div>
</div>
{/* Incidents */}
<div>
<h2 className="text-lg font-bold text-white mb-4">Past Incidents</h2>
<div className="space-y-3">
{INCIDENTS.map((inc) => (
<div key={inc.date} className="rounded-2xl border border-white/[0.06] bg-[#111118] p-5">
<div className="flex items-center gap-2 mb-2">
<span className="text-[10px] font-medium px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-400 uppercase">
{inc.severity}
</span>
<span className="text-xs text-white/30">{inc.date}</span>
</div>
<p className="text-sm font-semibold text-white mb-1">{inc.title}</p>
<p className="text-xs text-white/50 leading-relaxed">{inc.detail}</p>
</div>
))}
</div>
</div>
</div>
</div>
)
}
+67
View File
@@ -0,0 +1,67 @@
import Link from "next/link"
import { LegalPage, Section, LegalContact } from "@/components/marketing/legal"
import { LEGAL, SUBPROCESSORS } from "@/lib/legal"
export const metadata = {
title: "Sub-processors",
description: `The third-party sub-processors that ${LEGAL.entity} engages to process personal data on behalf of Customers of ${LEGAL.service}.`,
alternates: { canonical: "/subprocessors" },
}
export default function SubprocessorsPage() {
return (
<LegalPage
title="Sub-processors"
subtitle={`The third parties that ${LEGAL.entity} engages to process personal data on behalf of Customers of ${LEGAL.service}.`}
>
<Section id="overview" heading="1. Overview">
<p>
A <strong>sub-processor</strong> is a third party that we engage to process
personal data on behalf of the Customer in connection with the Service. When the
Customer uses {LEGAL.service}, the Customer grants us a general authorization to
engage the sub-processors listed below under the terms of our{" "}
<Link href="/dpa">Data Processing Addendum</Link>. Each sub-processor is bound by
data-protection obligations that are substantially equivalent to those we owe the
Customer.
</p>
</Section>
<Section id="current" heading="2. Current sub-processors">
<p>
The following third parties are the sub-processors currently engaged to process
personal data on the Customer&rsquo;s behalf:
</p>
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] overflow-hidden">
<div className="hidden sm:grid grid-cols-[1.2fr_2fr_1.4fr] gap-4 border-b border-white/[0.08] px-5 py-3 text-xs font-semibold uppercase tracking-wider text-white/40">
<span>Sub-processor</span>
<span>Purpose</span>
<span>Location</span>
</div>
{SUBPROCESSORS.map((sp, i) => (
<div
key={sp.name}
className={`grid grid-cols-1 gap-1 px-5 py-4 text-sm sm:grid-cols-[1.2fr_2fr_1.4fr] sm:gap-4 ${
i !== SUBPROCESSORS.length - 1 ? "border-b border-white/[0.04]" : ""
}`}
>
<span className="text-white/80 font-medium">{sp.name}</span>
<span className="text-white/50">{sp.purpose}</span>
<span className="text-white/50">{sp.location}</span>
</div>
))}
</div>
</Section>
<Section id="changes" heading="3. Changes to this list">
<p>
We update this page whenever our sub-processors change. The Customer may request
to be notified of additions to, or replacements of, our sub-processors, and may
object to a change on legitimate data-protection grounds, as described in our{" "}
<Link href="/dpa">Data Processing Addendum</Link>.
</p>
</Section>
<LegalContact email={LEGAL.dpoEmail} />
</LegalPage>
)
}
+4 -3
View File
@@ -2,15 +2,16 @@ import Link from "next/link"
import { Building2, CheckCircle2, Smartphone, Bell, FileText, ArrowRight } from "lucide-react"
export const metadata = {
title: "Tenant Portal — Property Management Network",
title: "Tenant Portal",
description: "Give your tenants a dedicated portal to pay rent, submit maintenance requests, and view lease details.",
alternates: { canonical: "/tenant-portal-info" },
}
const FEATURES = [
{ icon: CheckCircle2, color: "text-emerald-400", bg: "bg-emerald-500/10 border-emerald-500/20", title: "Online Rent Payment", desc: "Tenants pay rent securely via Stripe — card or bank transfer. Auto-receipts sent by email." },
{ icon: FileText, color: "text-indigo-400", bg: "bg-indigo-500/10 border-indigo-500/20", title: "Lease Documents", desc: "View and download lease agreements, addendums, and move-in checklists anytime." },
{ icon: Building2, color: "text-violet-400", bg: "bg-violet-500/10 border-violet-500/20", title: "Maintenance Requests", desc: "Submit maintenance issues with photos. Track status from open → in progress → resolved." },
{ icon: Bell, color: "text-amber-400", bg: "bg-amber-500/10 border-amber-500/20", title: "Smart Notifications", desc: "Rent due reminders, request updates, and landlord messages via email or WhatsApp." },
{ icon: Bell, color: "text-amber-400", bg: "bg-amber-500/10 border-amber-500/20", title: "Smart Notifications", desc: "Rent due reminders, request updates, and landlord messages delivered by email." },
{ icon: Smartphone, color: "text-blue-400", bg: "bg-blue-500/10 border-blue-500/20", title: "Mobile Friendly", desc: "Works perfectly on any device — no app download needed, just a secure link." },
{ icon: FileText, color: "text-rose-400", bg: "bg-rose-500/10 border-rose-500/20", title: "Payment History", desc: "Full history of all payments and receipts. Great for tenant records and disputes." },
]
@@ -98,7 +99,7 @@ export default function TenantPortalInfoPage() {
<div className="border-t border-white/[0.06] py-20">
<div className="mx-auto max-w-2xl px-6 text-center">
<h2 className="text-2xl font-bold text-white mb-4">Ready to give tenants a better experience?</h2>
<p className="text-white/40 mb-8">Free plan includes up to 2 properties and unlimited tenant portal access.</p>
<p className="text-white/40 mb-8">Free plan includes 1 property and unlimited tenant portal access.</p>
<Link
href="/signup"
className="inline-flex items-center gap-2 rounded-xl bg-indigo-600 px-8 py-3.5 text-sm font-semibold text-white hover:bg-indigo-500 transition-all hover:shadow-lg hover:shadow-indigo-500/25"
+304 -13
View File
@@ -1,17 +1,308 @@
export const metadata = { title: "Terms of Service — Property Management Network" }
import { LegalPage, Section, LegalContact } from "@/components/marketing/legal"
import { LEGAL } from "@/lib/legal"
export default function TermsPage() {
export const metadata = {
title: "Terms of Service",
description: `The terms and conditions that govern your access to and use of the ${LEGAL.service} platform.`,
alternates: { canonical: "/terms" },
}
export default function Page() {
return (
<div className="mx-auto max-w-3xl px-6 py-24">
<h1 className="text-3xl font-bold text-white mb-8">Terms of Service</h1>
<p className="text-white/50 text-sm leading-relaxed">
By using Property Management Network you agree to these terms. Property Management Network is provided as-is for property
management purposes. You are responsible for the accuracy of data you enter. Subscription fees
are billed monthly or as a one-time charge through Stripe. You may cancel at any time
cancellation takes effect at the end of your billing period. Lifetime plans are non-refundable
after 14 days. We reserve the right to suspend accounts that violate these terms.
For questions, contact us at support@propertymanagement.network.
</p>
</div>
<LegalPage
title="Terms of Service"
subtitle={`These Terms of Service govern your access to and use of the ${LEGAL.service} platform. Please read them carefully, because they form a binding agreement between you and ${LEGAL.entity}.`}
>
<Section heading="1. Agreement to these Terms">
<p>
These Terms of Service (the <strong>Terms</strong>) form a legally binding agreement between you and{" "}
{LEGAL.entity} (<strong>we</strong>, <strong>us</strong>, or <strong>our</strong>) and govern your access
to and use of the {LEGAL.service} platform, together with all related websites, applications, features,
and services (collectively, the <strong>Service</strong>). By creating an account, accessing, or using the
Service, you agree to be bound by these Terms. If you do not agree, you must not access or use the Service.
</p>
<p>
You represent that you are at least 18 years of age (or the age of legal majority in your jurisdiction) and
are capable of forming a binding contract. If you use the Service on behalf of a company, organization, or
other legal entity, you represent and warrant that you have the authority to bind that entity to these
Terms, and in that case <strong>you</strong> and <strong>Customer</strong> refer to that entity.
</p>
<p>
Throughout these Terms, <strong>you</strong> or <strong>Customer</strong> means the account holder (a
landlord or property manager); <strong>Tenant</strong> means an end user whose data the Customer manages;
and <strong>Customer Content</strong> means the data the Customer enters into or uploads to the Service.
</p>
</Section>
<Section heading="2. Description of the Service">
<p>
The Service is a software-as-a-service platform that helps landlords and property managers manage rental
properties, including features for organizing properties and units, tracking tenancies and leases,
recording payments and expenses, storing documents, sending notifications, generating AI-assisted
insights, and sharing information with Tenants through a token-based Tenant portal.
</p>
<p>
We may add, modify, or discontinue features of the Service from time to time. The Service is provided as an
online tool and is not a substitute for professional legal, financial, tax, accounting, or
property-management advice.
</p>
</Section>
<Section heading="3. Accounts and registration">
<p>
To use most features of the Service, you must create an account. You agree to provide accurate, current,
and complete information during registration and to keep that information up to date. You may register
using an email address and password or, where offered, through optional Google sign-in.
</p>
<p>
You are responsible for safeguarding your account credentials and for restricting access to your account.
You must keep your password confidential and notify us promptly at{" "}
<a href={`mailto:${LEGAL.securityEmail}`}>{LEGAL.securityEmail}</a> if you suspect any unauthorized use of
your account. You are responsible for all activity that occurs under your account, whether or not
authorized by you, to the extent permitted by applicable law.
</p>
</Section>
<Section heading="4. Subscriptions, plans, and billing">
<p>
The Service is offered under several plans: <strong>Starter</strong> (free), <strong>Pro</strong> ($29 per
month), <strong>Landlord</strong> ($59 per month), and <strong>Lifetime</strong> ($199 as a one-time
charge). The features and limits associated with each plan are described on our pricing page and may be
updated from time to time.
</p>
<p>
Paid subscriptions are billed in advance through our payment processor, <strong>Stripe</strong>. By
providing a payment method, you authorize us and Stripe to charge the applicable fees, together with any
applicable taxes, to that payment method. Monthly subscriptions renew automatically at the end of each
billing cycle until cancelled. You are responsible for all taxes, duties, and similar charges associated
with your use of the Service, other than taxes based on our net income.
</p>
<p>
We may change our fees or introduce new charges. If we increase the fees for a plan you are subscribed to,
we will provide prior notice, and the change will take effect on your next billing cycle. Your continued
use of the Service after a price change takes effect constitutes acceptance of the new fees. Cancellations
and refunds are governed by our <a href="/refund-policy">Refund &amp; Cancellation Policy</a>.
</p>
</Section>
<Section heading="5. Customer Content and responsibilities">
<p>
As between you and us, you retain all rights in and ownership of your <strong>Customer Content</strong>. You
grant us a limited, non-exclusive, worldwide license to host, store, process, transmit, display, and
otherwise use the Customer Content solely as necessary to provide, secure, and improve the Service and to
comply with your instructions and applicable law.
</p>
<p>
You are solely responsible for the accuracy, quality, and legality of your Customer Content and for the
means by which you acquired it. Where you enter or upload personal data relating to a{" "}
<strong>Tenant</strong> or any other individual, you represent and warrant that you have a lawful basis and,
where required, the necessary consent to do so, and that your use of the Service complies with all
applicable data-protection, housing, and landlord-tenant laws.
</p>
<p>
For personal data that you process through the Service, you act as the <strong>data controller</strong> and
we act as your processor. Our respective obligations are set out in our{" "}
<a href="/dpa">Data Processing Addendum</a>, and our general data practices are described in our{" "}
<a href="/privacy">Privacy Policy</a>.
</p>
</Section>
<Section heading="6. Acceptable use">
<p>
Your use of the Service must comply with our <a href="/acceptable-use">Acceptable Use Policy</a>, which is
incorporated into these Terms by reference. Among other things, you must not use the Service for any
unlawful purpose, to violate housing, fair-housing, anti-discrimination, or landlord-tenant laws, to harass
Tenants, or to compromise the security or integrity of the Service. A violation of the Acceptable Use Policy
is a violation of these Terms.
</p>
</Section>
<Section heading="7. Tenant portal">
<p>
The Service includes a token-based <strong>Tenant portal</strong> that allows you to share selected
information with a Tenant through a secure link. You control what information is shared and with whom, and
you are responsible for distributing token links only to the intended recipients and for revoking access
when it is no longer appropriate. We are not responsible for information disclosed as a result of your
sharing decisions or your handling of token links.
</p>
</Section>
<Section heading="8. Intellectual property">
<p>
The Service, including all software, designs, text, graphics, and other materials that we provide (but
excluding Customer Content), is owned by us or our licensors and is protected by intellectual-property
laws. Subject to your compliance with these Terms, we grant you a limited, revocable, non-exclusive,
non-transferable license to access and use the Service for your internal business purposes during the term
of your subscription. All rights not expressly granted are reserved.
</p>
<p>
If you choose to provide us with suggestions, ideas, or other feedback about the Service, you grant us a
perpetual, irrevocable, worldwide, royalty-free license to use and incorporate that feedback into the
Service without any obligation or compensation to you.
</p>
</Section>
<Section heading="9. Third-party services">
<p>
The Service relies on and integrates with third-party services, including <strong>Stripe</strong> for
payments, <strong>OpenAI</strong> for AI features, <strong>SMTP2GO</strong> for email delivery,{" "}
<strong>DigitalOcean</strong> for hosting and storage, and optional <strong>Google</strong> sign-in. Your
use of those services may be subject to their own terms and policies. We do not control and are not
responsible for third-party services, and we make no warranties regarding them. A current list of the
third parties that process personal data on our behalf is available on our{" "}
<a href="/subprocessors">Sub-processors</a> page.
</p>
</Section>
<Section heading="10. AI features">
<p>
Certain features of the Service use artificial-intelligence models (provided by OpenAI) to generate
insights, summaries, and suggestions. <strong>AI-generated outputs may be inaccurate, incomplete, or
otherwise unreliable, are provided for informational purposes only, and do not constitute legal, financial,
tax, or professional advice.</strong> You are responsible for reviewing and independently verifying any
AI-generated output before relying on it. Please review our <a href="/disclaimer">Disclaimer</a> for
further information.
</p>
</Section>
<Section heading="11. Privacy">
<p>
Our collection and use of personal information in connection with the Service is described in our{" "}
<a href="/privacy">Privacy Policy</a>. By using the Service, you acknowledge that you have read and
understood our Privacy Policy.
</p>
</Section>
<Section heading="12. Suspension and termination">
<p>
You may cancel your subscription or close your account at any time as described in our{" "}
<a href="/refund-policy">Refund &amp; Cancellation Policy</a>. We may suspend or terminate your access to
the Service, in whole or in part, if you breach these Terms, if your use poses a security, legal, or
operational risk, if required by law, or if you fail to pay fees when due.
</p>
<p>
Upon termination, your right to access and use the Service ceases. For a period of{" "}
{LEGAL.dataDeletionDays} days following termination, and where technically feasible, you may request an
export of your Customer Content, after which we may delete or de-identify it in accordance with our{" "}
<a href="/privacy">Privacy Policy</a> and <a href="/dpa">Data Processing Addendum</a>. Provisions that by
their nature should survive termination will survive.
</p>
</Section>
<Section heading="13. Disclaimer of warranties">
<p>
<strong>
THE SERVICE IS PROVIDED ON AN &ldquo;AS IS&rdquo; AND &ldquo;AS AVAILABLE&rdquo; BASIS, WITHOUT
WARRANTIES OF ANY KIND, WHETHER EXPRESS, IMPLIED, OR STATUTORY.
</strong>{" "}
To the maximum extent permitted by applicable law, we disclaim all warranties, including any implied
warranties of merchantability, fitness for a particular purpose, title, and non-infringement, and any
warranties arising out of course of dealing or usage of trade.
</p>
<p>
<strong>
We do not warrant that the Service will be uninterrupted, secure, error-free, or free of harmful
components, or that any AI-generated output or other results obtained through the Service will be accurate
or reliable.
</strong>{" "}
Some jurisdictions do not allow the exclusion of certain warranties, so some of the above exclusions may not
apply to you.
</p>
</Section>
<Section heading="14. Limitation of liability">
<p>
<strong>
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL WE BE LIABLE FOR ANY INDIRECT,
INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR ANY LOSS OF PROFITS, REVENUE,
DATA, GOODWILL, OR BUSINESS OPPORTUNITIES, ARISING OUT OF OR RELATING TO THESE TERMS OR THE SERVICE,
WHETHER BASED ON CONTRACT, TORT, OR ANY OTHER LEGAL THEORY, EVEN IF WE HAVE BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
</strong>
</p>
<p>
<strong>
OUR TOTAL AGGREGATE LIABILITY ARISING OUT OF OR RELATING TO THESE TERMS OR THE SERVICE WILL NOT EXCEED THE
GREATER OF (A) THE TOTAL FEES YOU PAID TO US FOR THE SERVICE IN THE TWELVE (12) MONTHS IMMEDIATELY
PRECEDING THE EVENT GIVING RISE TO THE CLAIM, OR (B) ONE HUNDRED U.S. DOLLARS (USD 100).
</strong>{" "}
Some jurisdictions do not allow the limitation or exclusion of liability for certain damages, so some of the
above limitations may not apply to you.
</p>
</Section>
<Section heading="15. Indemnification">
<p>
You agree to defend, indemnify, and hold harmless {LEGAL.entity} and its officers, directors, employees,
and agents from and against any claims, liabilities, damages, losses, and expenses (including reasonable
legal fees) arising out of or relating to: (a) your Customer Content; (b) your use of the Service; (c) your
violation of these Terms or applicable law; and (d) any claim brought by a Tenant or other third party
arising out of your use of the Service or your handling of their data.
</p>
</Section>
<Section heading="16. Changes to the Service and to these Terms">
<p>
We may modify, update, or discontinue the Service or any part of it at any time. We may also revise these
Terms from time to time. When we make material changes, we will provide notice by reasonable means, such as
by posting the updated Terms with a new effective date or by notifying you through the Service or by email.
Your continued use of the Service after the changes take effect constitutes acceptance of the revised
Terms.
</p>
</Section>
<Section heading="17. Governing law and dispute resolution">
<p>
These Terms and any dispute arising out of or relating to them or the Service are governed by the laws of{" "}
{LEGAL.governingLaw}, without regard to its conflict-of-laws principles. Subject to the arbitration
provision below, the parties submit to the exclusive jurisdiction and venue of {LEGAL.forum}.
</p>
<p>
<strong>Informal resolution.</strong> Before initiating any formal proceeding, you agree to first contact
us at <a href={`mailto:${LEGAL.legalEmail}`}>{LEGAL.legalEmail}</a> and to attempt in good faith to resolve
the dispute informally. Most concerns can be resolved this way.
</p>
<p>
<strong>Binding arbitration and class-action waiver.</strong> To the fullest extent permitted by applicable
law, any dispute that is not resolved informally will be settled by final and binding arbitration on an
individual basis, rather than in court, except that either party may bring an individual claim in
small-claims court. <strong>YOU AND WE EACH WAIVE ANY RIGHT TO A JURY TRIAL AND TO PARTICIPATE IN A CLASS,
COLLECTIVE, OR REPRESENTATIVE ACTION.</strong> Arbitration will be conducted by a recognized arbitration
body under its applicable rules, and judgment on the award may be entered in any court of competent
jurisdiction.
</p>
<p>
Nothing in this section limits any statutory rights that cannot be waived under the law that applies to you.
Consumers in certain jurisdictions may have non-waivable rights to bring claims in their local courts or
before their local authorities, and this section does not override those rights.
</p>
</Section>
<Section heading="18. Miscellaneous">
<p>
These Terms, together with the policies incorporated by reference (including the{" "}
<a href="/acceptable-use">Acceptable Use Policy</a>, <a href="/privacy">Privacy Policy</a>,{" "}
<a href="/dpa">Data Processing Addendum</a>, and <a href="/refund-policy">Refund &amp; Cancellation
Policy</a>), constitute the entire agreement between you and us regarding the Service and supersede all
prior agreements on that subject.
</p>
<p>
If any provision of these Terms is held to be invalid or unenforceable, that provision will be limited or
eliminated to the minimum extent necessary, and the remaining provisions will remain in full force and
effect. You may not assign or transfer these Terms without our prior written consent; we may assign these
Terms in connection with a merger, acquisition, or sale of assets. Our failure to enforce any provision is
not a waiver of our right to do so later.
</p>
<p>
We will not be liable for any failure or delay in performance caused by events beyond our reasonable
control, including acts of God, natural disasters, outages, or the failure of third-party services (a{" "}
<strong>force majeure</strong> event). Notices to you may be given through the Service or by email to the
address associated with your account; notices to us must be sent to{" "}
<a href={`mailto:${LEGAL.legalEmail}`}>{LEGAL.legalEmail}</a>.
</p>
</Section>
<LegalContact email={LEGAL.legalEmail} />
</LegalPage>
)
}