@pylonsync/create-pylon 0.3.308 → 0.3.310

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonsync/create-pylon",
3
- "version": "0.3.308",
3
+ "version": "0.3.310",
4
4
  "description": "Scaffold a new Pylon app — realtime backend + web/mobile/expo frontends in one command. Run via `npm create @pylonsync/pylon@latest`.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -53,6 +53,11 @@ export const generateMetadata: GenerateMetadata = async ({
53
53
  };
54
54
  };
55
55
 
56
+ // Public case study, no auth read → an ISR candidate. `revalidate` serves it
57
+ // from cache with stale-while-revalidate and makes <Link>'s prefetch reusable,
58
+ // so a click off the work grid hits cache instead of a live render.
59
+ export const revalidate = 300;
60
+
56
61
  const WRAP_NARROW = "mx-auto w-full max-w-3xl px-6";
57
62
 
58
63
  function CaseStudy({
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import React, { useState } from "react";
3
+ import React, { useEffect, useState } from "react";
4
4
  import { db } from "@pylonsync/react";
5
5
  import {
6
6
  passwordLogin,
@@ -29,6 +29,26 @@ export function AuthForm({ mode }: { mode: "login" | "signup" }) {
29
29
  const [password, setPassword] = useState("");
30
30
  const [error, setError] = useState<string | null>(null);
31
31
  const [pending, setPending] = useState(false);
32
+ // OAuth buttons render only for providers the server actually has
33
+ // configured (GET /api/auth/providers) — an unconfigured provider's
34
+ // login URL is a JSON error dead-end, not a login page. `null` while
35
+ // loading so nothing flashes in.
36
+ const [providers, setProviders] = useState<string[] | null>(null);
37
+
38
+ useEffect(() => {
39
+ let alive = true;
40
+ fetch("/api/auth/providers")
41
+ .then((r) => (r.ok ? r.json() : []))
42
+ .then((list: Array<{ provider: string }>) => {
43
+ if (alive) setProviders(list.map((p) => p.provider));
44
+ })
45
+ .catch(() => {
46
+ if (alive) setProviders([]);
47
+ });
48
+ return () => {
49
+ alive = false;
50
+ };
51
+ }, []);
32
52
 
33
53
  async function onSubmit(e: React.FormEvent) {
34
54
  e.preventDefault();
@@ -114,22 +134,38 @@ export function AuthForm({ mode }: { mode: "login" | "signup" }) {
114
134
  </button>
115
135
  </form>
116
136
 
117
- <div className="flex items-center gap-3 text-[11px] uppercase tracking-wide text-zinc-400">
118
- <span className="h-px flex-1 bg-zinc-200" />
119
- or continue with
120
- <span className="h-px flex-1 bg-zinc-200" />
121
- </div>
137
+ {/* Social sign-in only for providers the server reports as
138
+ configured. Enable Google with PYLON_OAUTH_GOOGLE_CLIENT_ID /
139
+ _CLIENT_SECRET / _REDIRECT (GitHub: PYLON_OAUTH_GITHUB_*) and the
140
+ button appears with zero code changes. */}
141
+ {providers && providers.length > 0 ? (
142
+ <>
143
+ <div className="flex items-center gap-3 text-[11px] uppercase tracking-wide text-zinc-400">
144
+ <span className="h-px flex-1 bg-zinc-200" />
145
+ or continue with
146
+ <span className="h-px flex-1 bg-zinc-200" />
147
+ </div>
122
148
 
123
- {/* Social sign-in. The Google provider must be configured (set
124
- PYLON_OAUTH_GOOGLE_CLIENT_ID / _CLIENT_SECRET / _REDIRECT) — until then
125
- this button returns a helpful "configure the provider" error. */}
126
- <a
127
- href="/api/auth/login/google?callback=/dashboard&redirect=1"
128
- className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-lg border border-zinc-300 bg-white text-sm font-medium text-zinc-900 transition-colors hover:bg-zinc-50"
129
- >
130
- <GoogleIcon />
131
- Google
132
- </a>
149
+ {providers.includes("google") ? (
150
+ <a
151
+ href="/api/auth/login/google?callback=/dashboard&redirect=1"
152
+ className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-lg border border-zinc-300 bg-white text-sm font-medium text-zinc-900 transition-colors hover:bg-zinc-50"
153
+ >
154
+ <GoogleIcon />
155
+ Google
156
+ </a>
157
+ ) : null}
158
+ {providers.includes("github") ? (
159
+ <a
160
+ href="/api/auth/login/github?callback=/dashboard&redirect=1"
161
+ className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-lg border border-zinc-300 bg-white text-sm font-medium text-zinc-900 transition-colors hover:bg-zinc-50"
162
+ >
163
+ <GitHubIcon />
164
+ GitHub
165
+ </a>
166
+ ) : null}
167
+ </>
168
+ ) : null}
133
169
  </div>
134
170
  );
135
171
  }
@@ -194,6 +230,14 @@ function LockIcon() {
194
230
  );
195
231
  }
196
232
 
233
+ function GitHubIcon() {
234
+ return (
235
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
236
+ <path d="M12 1C5.92 1 1 5.92 1 12c0 4.87 3.15 9 7.52 10.45.55.1.75-.24.75-.53 0-.26-.01-.96-.01-1.88-3.06.66-3.71-1.48-3.71-1.48-.5-1.27-1.22-1.61-1.22-1.61-1-.68.08-.67.08-.67 1.1.08 1.68 1.13 1.68 1.13.98 1.68 2.57 1.19 3.2.91.1-.71.38-1.19.69-1.47-2.44-.28-5.01-1.22-5.01-5.44 0-1.2.43-2.18 1.13-2.95-.11-.28-.49-1.4.11-2.91 0 0 .92-.3 3.03 1.13a10.5 10.5 0 0 1 5.52 0c2.1-1.43 3.03-1.13 3.03-1.13.6 1.51.22 2.63.11 2.91.7.77 1.13 1.75 1.13 2.95 0 4.23-2.58 5.16-5.03 5.43.39.34.74 1 .74 2.02 0 1.47-.01 2.65-.01 3.01 0 .29.2.64.76.53A11.01 11.01 0 0 0 23 12c0-6.08-4.92-11-11-11z" />
237
+ </svg>
238
+ );
239
+ }
240
+
197
241
  function GoogleIcon() {
198
242
  return (
199
243
  <svg width="16" height="16" viewBox="0 0 24 24" aria-hidden>
@@ -31,6 +31,14 @@ async function resolveListing(
31
31
  );
32
32
  }
33
33
 
34
+ // The listing page is anonymous + public (the watch button + offer panel are
35
+ // client islands with their own auth), so its SSR output is shared across
36
+ // visitors — an ISR candidate. `revalidate` serves it from cache, emits
37
+ // stale-while-revalidate, and makes <Link>'s prefetch reusable so a click hits
38
+ // cache instead of a live render. Short TTL because a listing's sold status is
39
+ // time-sensitive; the realtime offer panel keeps the live bits fresh regardless.
40
+ export const revalidate = 60;
41
+
34
42
  // Data-driven SEO: the title + description come from the listing itself,
35
43
  // fetched on the server. `generateMetadata` is handed the same PageProps as
36
44
  // the page (params + serverData), so it reads the row directly.