mirror of
https://github.com/hanzoai/cal.git
synced 2026-10-16 20:43:01 +00:00
feat: cal.hanzo.ai — Cal's Booker on native Hanzo Base
The public booking SPA: Cal.com's real <Booker> UI powered entirely by the
native Hanzo Base backend at /v1/calendar (no Cal Prisma/Postgres/Node API).
- packages/calendar: @hanzo/calendar, the debranded fork surface — a thin
Hanzo-namespaced wrapper over published @calcom/atoms (MIT, see NOTICE) that
re-exports Provider/Booker under Hanzo names for public, no-OAuth booking.
- app: Vite + React SPA. /{owner}/{slug} renders the Booker against Base;
/booking/{uid} is the confirmation + cancel view.
- Dockerfile: FROM ghcr.io/hanzoai/spa, COPY app/dist /public.
Free-booking Stripe peers are stubbed so the bundle is self-contained/CSP-clean.
Only MIT @calcom code; Cal copyright preserved in NOTICE.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
node_modules/
|
||||
dist/
|
||||
app/dist/
|
||||
*.tsbuildinfo
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# cal.hanzo.ai — Cal's <Booker> (debranded as @hanzo/calendar) as a static SPA,
|
||||
# served by hanzoai/spa (Go static server, :3000). `pnpm build` writes the Vite
|
||||
# bundle to app/dist. The booking (/{host}/{event}) and manage (/booking/{uid})
|
||||
# routes are client-rendered SPA shells; hanzoai/spa falls back to index.html for
|
||||
# any client-routed path so the router resolves them and the atoms fetch on mount.
|
||||
FROM ghcr.io/hanzoai/spa:sha-b9d3a13-amd64
|
||||
COPY app/dist /public
|
||||
ENV PORT=3000
|
||||
ENV ROOT=/public
|
||||
# Allow same-origin + inline bootstrap (React hydration) and connect-src https: so
|
||||
# the browser can reach the Base booking API at cal.hanzo.ai/v1/calendar.
|
||||
ENV HANZO_STATIC_CSP="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https:; frame-src 'self'; media-src 'self' https:"
|
||||
EXPOSE 3000
|
||||
@@ -0,0 +1,34 @@
|
||||
# cal.hanzo.ai — Cal's Booker on native Hanzo Base
|
||||
|
||||
Public booking page: Cal.com's real `<Booker>` UI, powered entirely by the native
|
||||
Hanzo Base backend at `/v1/calendar`. No Cal Prisma/Postgres, no Cal Node API — the
|
||||
booking is created in Base.
|
||||
|
||||
## Layout
|
||||
- `packages/calendar` — **`@hanzo/calendar`**, the debranded fork surface. A thin
|
||||
Hanzo-namespaced wrapper over the published `@calcom/atoms` (MIT — see `NOTICE`):
|
||||
re-exports `Provider` (Cal's `CalProvider`, defaulted for public no-OAuth booking)
|
||||
and `Booker` under Hanzo names, and owns the atoms' style side-effect import. The
|
||||
SPA imports ONLY from here, never `@calcom/atoms` directly.
|
||||
- `app` — the Vite + React SPA (`@hanzo/cal-app`). Routes: `/` landing,
|
||||
`/{owner}/{slug}` the Booker, `/booking/{uid}` confirmation + cancel.
|
||||
- `Dockerfile` — `FROM ghcr.io/hanzoai/spa`, `COPY app/dist /public` (SPA fallback).
|
||||
|
||||
## Backend contract
|
||||
`<Provider apiUrl>` sets `axios.defaults.baseURL`, so every atom request hits our
|
||||
host. Point it at Base's `/v1/calendar` (default `https://cal.hanzo.ai/v1/calendar`,
|
||||
override with `VITE_CAL_API`). The backend is the `calendar` plugin in
|
||||
`github.com/hanzoai/base` (`plugins/calendar`), which speaks Cal's API-v2 shapes.
|
||||
|
||||
## Build
|
||||
```
|
||||
pnpm install
|
||||
pnpm build # -> app/dist
|
||||
```
|
||||
Public booking is free, so Cal's optional Stripe peers are aliased to local no-op
|
||||
stubs (`app/src/stripe-*.ts`) — the bundle stays self-contained and CSP-clean.
|
||||
|
||||
## Fork discipline
|
||||
Only MIT `@calcom/atoms` is used; no `ee/` / non-MIT Cal code. Cal's copyright stays
|
||||
in `NOTICE`, Hanzo's added. Phase-1 milestone is a local/workspace `@hanzo/calendar`;
|
||||
the immutable npm publish + `github.com/hanzoai/calendar` repo are the follow-on.
|
||||
@@ -0,0 +1,30 @@
|
||||
Hanzo Calendar (@hanzo/calendar, cal.hanzo.ai)
|
||||
Copyright (c) 2020-present Hanzo AI, Inc.
|
||||
|
||||
This product includes and derives from Cal.com's open-source scheduling atoms
|
||||
(@calcom/atoms), used under the MIT License:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-present Cal.com, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
Only MIT-licensed Cal.com code is used. No Cal.com enterprise (ee/) or otherwise
|
||||
non-MIT source is included or derived from.
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Book a time · Hanzo</title>
|
||||
<!-- Cal's atoms bundle expects Node-ish globals; shim them before any module
|
||||
runs so the browser build never touches an undefined `process`/`global`. -->
|
||||
<script>
|
||||
window.global = window;
|
||||
window.process = window.process || { env: { NODE_ENV: "production" } };
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@hanzo/cal-app",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --host --port 3000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzo/calendar": "workspace:*",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-router-dom": "6.28.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "18.3.12",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@vitejs/plugin-react": "4.3.4",
|
||||
"typescript": "5.6.3",
|
||||
"vite": "5.4.11"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
|
||||
import { Booking } from "./pages/Booking";
|
||||
import { Confirmation } from "./pages/Confirmation";
|
||||
import { Home } from "./pages/Home";
|
||||
|
||||
// Routes:
|
||||
// / landing
|
||||
// /booking/:uid booking confirmation / manage (more specific — matched first)
|
||||
// /:owner/:slug the public Booker for one event type
|
||||
export function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/booking/:uid" element={<Confirmation />} />
|
||||
<Route path="/:owner/:slug" element={<Booking />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// The native Hanzo Base booking API. Cal's atoms set axios.defaults.baseURL to this
|
||||
// via <Provider apiUrl>; the confirmation view fetches it directly. Override for
|
||||
// local development with VITE_CAL_API (e.g. http://localhost:8090/v1/calendar).
|
||||
export const calendarApi =
|
||||
import.meta.env.VITE_CAL_API || "https://cal.hanzo.ai/v1/calendar";
|
||||
@@ -0,0 +1,11 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import { App } from "./App";
|
||||
import "./styles.css";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Booker, Provider } from "@hanzo/calendar";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import { calendarApi } from "../api";
|
||||
|
||||
// The public booking page: Cal's real <Booker> for /{owner}/{slug}, pointed at the
|
||||
// native Base backend. No access token — public booking needs no managed-user OAuth.
|
||||
// On success we route to the confirmation view by the new booking's uid.
|
||||
export function Booking() {
|
||||
const { owner = "", slug = "" } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Provider apiUrl={calendarApi}>
|
||||
<Booker
|
||||
username={owner}
|
||||
eventSlug={slug}
|
||||
isTeamEvent={false}
|
||||
onCreateBookingSuccess={(res: { data?: { uid?: string } }) => {
|
||||
const uid = res?.data?.uid;
|
||||
if (uid) navigate(`/booking/${uid}`);
|
||||
}}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { calendarApi } from "../api";
|
||||
|
||||
type Booking = {
|
||||
uid: string;
|
||||
title: string;
|
||||
status: string;
|
||||
startTime?: string;
|
||||
start?: string;
|
||||
location?: string;
|
||||
attendees?: { name?: string; email?: string }[];
|
||||
};
|
||||
|
||||
// The booking confirmation / manage view for /booking/:uid. The uid is the booking's
|
||||
// opaque capability, so Base reveals the real meeting location here (never before
|
||||
// booking). Offers cancellation.
|
||||
export function Confirmation() {
|
||||
const { uid = "" } = useParams();
|
||||
const [booking, setBooking] = useState<Booking | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [canceling, setCanceling] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${calendarApi}/bookings/${encodeURIComponent(uid)}`)
|
||||
.then((r) => r.json())
|
||||
.then((res) => {
|
||||
if (res.status === "success") setBooking(res.data);
|
||||
else setError(res.error?.message ?? "Booking not found");
|
||||
})
|
||||
.catch(() => setError("Booking not found"));
|
||||
}, [uid]);
|
||||
|
||||
const cancel = () => {
|
||||
setCanceling(true);
|
||||
fetch(`${calendarApi}/bookings/${encodeURIComponent(uid)}/cancel`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ reason: "" }),
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then((res) => {
|
||||
if (res.status === "success") setBooking(res.data);
|
||||
else setError(res.error?.message ?? "Could not cancel");
|
||||
})
|
||||
.catch(() => setError("Could not cancel"))
|
||||
.finally(() => setCanceling(false));
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<main className="shell">
|
||||
<div className="card">
|
||||
<p className="muted">{error}</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
if (!booking) {
|
||||
return (
|
||||
<main className="shell">
|
||||
<div className="card">
|
||||
<p className="muted">Loading…</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
const canceled = booking.status === "cancelled";
|
||||
const when = new Date(booking.startTime ?? booking.start ?? "");
|
||||
const attendee = booking.attendees?.[0];
|
||||
|
||||
return (
|
||||
<main className="shell">
|
||||
<div className="card">
|
||||
<div className={`badge ${canceled ? "badge-off" : "badge-on"}`}>
|
||||
{canceled ? "Canceled" : "Confirmed"}
|
||||
</div>
|
||||
<h1>{booking.title}</h1>
|
||||
<p className="when">
|
||||
{when.toLocaleString(undefined, {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</p>
|
||||
{booking.location ? <p className="loc">{booking.location}</p> : null}
|
||||
{attendee ? (
|
||||
<p className="muted">
|
||||
{attendee.name}
|
||||
{attendee.email ? ` · ${attendee.email}` : ""}
|
||||
</p>
|
||||
) : null}
|
||||
{!canceled ? (
|
||||
<button className="cancel" onClick={cancel} disabled={canceling}>
|
||||
{canceling ? "Canceling…" : "Cancel booking"}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// The landing page. Public booking always happens at /{owner}/{slug}; this is just
|
||||
// a friendly root.
|
||||
export function Home() {
|
||||
return (
|
||||
<main className="shell">
|
||||
<div className="card">
|
||||
<h1>Book a time</h1>
|
||||
<p className="muted">
|
||||
Open a host's booking link — <code>cal.hanzo.ai/{host}/{event}</code>{" "}
|
||||
— to pick a time.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
// Public booking is free — no Stripe payment UI is ever rendered — so these no-op
|
||||
// stubs satisfy @calcom/atoms' optional @stripe/react-stripe-js imports (Elements,
|
||||
// PaymentElement, useStripe, useElements) and keep the bundle self-contained. Paid
|
||||
// events are out of Phase 1 scope.
|
||||
export const Elements = ({ children }: { children?: ReactNode }) => <>{children}</>;
|
||||
export const PaymentElement = (): null => null;
|
||||
export const useStripe = () => null;
|
||||
export const useElements = () => null;
|
||||
@@ -0,0 +1,6 @@
|
||||
// Public booking is free — no payments — so the Stripe SDK is never loaded. This
|
||||
// stub satisfies @calcom/atoms' optional `@stripe/stripe-js` import at build time
|
||||
// and keeps the bundle self-contained (no external SDK, CSP-clean). Paid events are
|
||||
// out of Phase 1 scope; wiring real Stripe is a later, deliberate step.
|
||||
export const loadStripe = () => Promise.resolve(null);
|
||||
export default { loadStripe };
|
||||
@@ -0,0 +1,131 @@
|
||||
:root {
|
||||
--bg: #f6f6f8;
|
||||
--card: #ffffff;
|
||||
--ink: #1a1a1a;
|
||||
--muted: #6b7280;
|
||||
--line: #e5e7eb;
|
||||
--accent: #111827;
|
||||
--on: #047857;
|
||||
--off: #b91c1c;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
||||
Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* The shell hosts our own light pages (landing, confirmation). The Booker itself
|
||||
ships Cal's Tailwind styling and renders inside its own wrapper. */
|
||||
.shell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
padding: 32px;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.card h1 {
|
||||
font-size: 22px;
|
||||
margin: 8px 0 6px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.when {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.loc {
|
||||
font-size: 14px;
|
||||
margin: 4px 0 12px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f1f1f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.badge-on {
|
||||
background: #ecfdf5;
|
||||
color: var(--on);
|
||||
}
|
||||
|
||||
.badge-off {
|
||||
background: #fef2f2;
|
||||
color: var(--off);
|
||||
}
|
||||
|
||||
.cancel {
|
||||
margin-top: 20px;
|
||||
background: transparent;
|
||||
color: var(--off);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cancel:hover {
|
||||
border-color: var(--off);
|
||||
}
|
||||
|
||||
.cancel:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #0b0b0d;
|
||||
--card: #151517;
|
||||
--ink: #f3f4f6;
|
||||
--muted: #9ca3af;
|
||||
--line: #26262b;
|
||||
}
|
||||
code {
|
||||
background: #26262b;
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src", "../packages/calendar/src"]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
const stripeStub = fileURLToPath(new URL("./src/stripe-stub.ts", import.meta.url));
|
||||
const stripeReactStub = fileURLToPath(new URL("./src/stripe-react-stub.tsx", import.meta.url));
|
||||
|
||||
// The @hanzo/calendar workspace package is TS source; pnpm symlinks it to its real
|
||||
// path outside node_modules, so Vite transpiles it. @calcom/atoms is consumed as
|
||||
// its pre-built ESM bundle. NODE_ENV is pinned so React and the atoms tree-shake to
|
||||
// their production paths in the browser.
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
// Public booking is free; stub Stripe so the optional peer import resolves
|
||||
// and the bundle stays self-contained (no external SDK, CSP-clean).
|
||||
"@stripe/stripe-js/pure": stripeStub,
|
||||
"@stripe/stripe-js": stripeStub,
|
||||
"@stripe/react-stripe-js": stripeReactStub,
|
||||
},
|
||||
},
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify("production"),
|
||||
},
|
||||
build: {
|
||||
outDir: "dist",
|
||||
sourcemap: false,
|
||||
chunkSizeWarningLimit: 4000,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "hanzo-calendar",
|
||||
"private": true,
|
||||
"description": "cal.hanzo.ai — Cal.com's Booker (debranded as @hanzo/calendar) over the native Hanzo Base /v1/calendar backend.",
|
||||
"scripts": {
|
||||
"build": "pnpm --filter @hanzo/cal-app build",
|
||||
"dev": "pnpm --filter @hanzo/cal-app dev"
|
||||
},
|
||||
"packageManager": "[email protected]"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@hanzo/calendar
|
||||
Copyright (c) 2020-present Hanzo AI, Inc.
|
||||
|
||||
Debranded fork surface of Cal.com's @calcom/atoms, used under the MIT License:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-present Cal.com, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@hanzo/calendar",
|
||||
"version": "0.1.0",
|
||||
"description": "Hanzo-branded booking components — a debranded fork surface of Cal.com's @calcom/atoms, pointed at the native Hanzo Base /v1/calendar backend.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
"*.css"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@calcom/atoms": "2.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// @hanzo/calendar — Hanzo-branded booking components.
|
||||
//
|
||||
// This is the debranded fork surface of Cal.com's @calcom/atoms (MIT — see the
|
||||
// NOTICE file). The SPA at cal.hanzo.ai imports ONLY from here, never from
|
||||
// @calcom/atoms directly: the Cal provider and Booker are re-exported under Hanzo
|
||||
// names and defaulted for public, no-OAuth booking against the native Hanzo Base
|
||||
// backend at /v1/calendar. Cal's look and feel (its Tailwind styling) ships with
|
||||
// the atoms and is intentionally preserved — this is Cal's Booker, Hanzo-hosted.
|
||||
|
||||
import { CalProvider, Booker as CalBooker } from "@calcom/atoms";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
|
||||
// Cal's atom styles (Tailwind, pre-built). Importing here means a consumer only has
|
||||
// to import "@hanzo/calendar" to get both the components and their look.
|
||||
import "@calcom/atoms/globals.min.css";
|
||||
|
||||
/** Default Base backend for the booking API. Override with VITE_CAL_API. */
|
||||
export const DEFAULT_CALENDAR_API = "https://cal.hanzo.ai/v1/calendar";
|
||||
|
||||
export type ProviderProps = {
|
||||
/** Base backend booking API, e.g. https://cal.hanzo.ai/v1/calendar */
|
||||
apiUrl?: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provider wraps Cal's CalProvider for public booking: no OAuth client, no managed
|
||||
* user — just an API base pointed at Hanzo Base. Cal's provider sets
|
||||
* axios.defaults.baseURL = apiUrl, so every Booker request hits our host, and it
|
||||
* initializes from apiUrl alone (an empty clientId skips the OAuth client fetch).
|
||||
*/
|
||||
export function Provider({ apiUrl = DEFAULT_CALENDAR_API, children }: ProviderProps) {
|
||||
return (
|
||||
<CalProvider clientId="" options={{ apiUrl }} version="2024-08-13">
|
||||
{children}
|
||||
</CalProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/** Booker is Cal's public Booker, re-exported under the Hanzo surface. */
|
||||
export const Booker = CalBooker;
|
||||
export type BookerProps = ComponentProps<typeof CalBooker>;
|
||||
Generated
+3770
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- "packages/*"
|
||||
- "app"
|
||||
Reference in New Issue
Block a user