@pantheon-systems/create-p1-starter-kit 0.13.0 → 0.14.0

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.
@@ -0,0 +1,124 @@
1
+ /* Styling for the welcome block and the sign-in screen that shares its layout.
2
+ Both render inside a host site whose own body styles they must not inherit —
3
+ a dark-themed host would otherwise leave this dark text on a dark background —
4
+ so the surface sets its own background as well as its foreground. */
5
+
6
+ .surface {
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: center;
10
+ justify-content: center;
11
+ width: 100%;
12
+ min-height: 100vh;
13
+ padding: 4rem 2rem;
14
+ background: #ffffff;
15
+ color: #1a1a2e;
16
+ font-family: "Inter", system-ui, sans-serif;
17
+ text-align: center;
18
+ }
19
+
20
+ .inner {
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: center;
24
+ width: 100%;
25
+ max-width: 620px;
26
+ margin: 0 auto;
27
+ }
28
+
29
+ .lockup {
30
+ display: block;
31
+ height: 1.75rem;
32
+ width: auto;
33
+ margin-bottom: 1.5rem;
34
+ }
35
+
36
+ .heading {
37
+ margin: 0 0 1rem;
38
+ font-size: 2.5rem;
39
+ font-weight: 600;
40
+ line-height: 1.08;
41
+ }
42
+
43
+ .description {
44
+ max-width: 54ch;
45
+ margin: 0;
46
+ font-size: 1rem;
47
+ line-height: 1.5rem;
48
+ color: #5a5a6e;
49
+ }
50
+
51
+ .actions {
52
+ display: flex;
53
+ justify-content: center;
54
+ gap: 0.75rem;
55
+ margin-top: 2rem;
56
+ }
57
+
58
+ .button {
59
+ display: inline-flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ gap: 0.5rem;
63
+ height: 3rem;
64
+ padding: 0 1.5rem;
65
+ border: 1px solid #1a1a2e;
66
+ border-radius: 9999px;
67
+ background: #1a1a2e;
68
+ color: #ffffff;
69
+ font-family: "Inter", system-ui, sans-serif;
70
+ font-size: 1.125rem;
71
+ font-weight: 500;
72
+ line-height: 1;
73
+ white-space: nowrap;
74
+ cursor: pointer;
75
+ transition:
76
+ background-color 0.2s,
77
+ border-color 0.2s;
78
+ }
79
+
80
+ .button:hover {
81
+ background: #2d2d44;
82
+ border-color: #2d2d44;
83
+ }
84
+
85
+ .button:focus-visible {
86
+ outline: 1px solid #2563eb;
87
+ outline-offset: 1px;
88
+ }
89
+
90
+ .button:disabled {
91
+ opacity: 0.4;
92
+ cursor: not-allowed;
93
+ }
94
+
95
+ .buttonSecondary {
96
+ border-color: #d0d0d8;
97
+ background: #ffffff;
98
+ color: #1a1a2e;
99
+ }
100
+
101
+ .buttonSecondary:hover {
102
+ background: rgba(26, 26, 46, 0.06);
103
+ border-color: #d0d0d8;
104
+ }
105
+
106
+ .footnote {
107
+ margin-top: 3rem;
108
+ font-size: 0.875rem;
109
+ color: #5a5a6e;
110
+ }
111
+
112
+ .link {
113
+ color: #2563eb;
114
+ text-decoration: underline;
115
+ }
116
+
117
+ .error {
118
+ margin-top: 1rem;
119
+ padding: 0.5rem 0.75rem;
120
+ border-radius: 0.375rem;
121
+ background: #fef2f2;
122
+ color: #dc2626;
123
+ font-size: 0.875rem;
124
+ }
@@ -38,6 +38,9 @@ export default tseslint.config(
38
38
  {
39
39
  files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.mjs'],
40
40
  plugins: { import: importPlugin },
41
+ // Packages alias their own source as @/… in tsconfig paths. Without this the
42
+ // import plugin reads those as third-party and sorts them after relative ones.
43
+ settings: { 'import/internal-regex': '^@/' },
41
44
  languageOptions: {
42
45
  ecmaVersion: 'latest',
43
46
  sourceType: 'module',
@@ -71,7 +74,12 @@ export default tseslint.config(
71
74
  'import/extensions': 'off',
72
75
  'import/prefer-default-export': 'off',
73
76
  'import/no-unresolved': 'off',
74
- 'import/order': 'warn',
77
+ // 'internal' is absent from the rule's default group list, so aliased
78
+ // imports would otherwise rank below every relative import.
79
+ 'import/order': [
80
+ 'warn',
81
+ { groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'] },
82
+ ],
75
83
  'import/no-extraneous-dependencies': 'warn',
76
84
 
77
85
  // General ESLint rules
@@ -0,0 +1,32 @@
1
+ import type { AuthUser } from "@pantheon-systems/puck-css";
2
+
3
+ export interface FlagContext {
4
+ kind: "user";
5
+ key: string;
6
+ email?: string;
7
+ anonymous?: boolean;
8
+ }
9
+
10
+ /**
11
+ * Build the LaunchDarkly evaluation context for the signed-in user.
12
+ *
13
+ * Keyed on the lowercased email because LaunchDarkly matches individual targets on
14
+ * the key alone, and the rest of P1 identifies people to LaunchDarkly the same way.
15
+ * Keying on anything else makes an email entered in the targeting UI match nothing,
16
+ * with no error to say so.
17
+ *
18
+ * Falls back to the user id when email is absent, which AuthUser allows, so those
19
+ * users stay individually targetable instead of sharing the anonymous context.
20
+ */
21
+ export function buildFlagContext(user: AuthUser | null): FlagContext {
22
+ if (!user) {
23
+ return { kind: "user", key: "anonymous", anonymous: true };
24
+ }
25
+
26
+ const email = user.email?.trim();
27
+ return {
28
+ kind: "user",
29
+ key: email ? email.toLowerCase() : user.id,
30
+ ...(email && { email }),
31
+ };
32
+ }
@@ -15,12 +15,12 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@pantheon-systems/cpub-react-sdk": "^5.2.1",
18
- "@pantheon-systems/css-client": "^0.13.0",
18
+ "@pantheon-systems/css-client": "^0.14.0",
19
19
  "@pantheon-systems/p1-ai-chat": "^0.6.0",
20
20
  "@pantheon-systems/p1-media": "^0.4.5",
21
- "@pantheon-systems/p1-next-sdk": "^0.13.0",
21
+ "@pantheon-systems/p1-next-sdk": "^0.14.0",
22
22
  "@pantheon-systems/pds-toolkit-react": "2.0.0-alpha.66",
23
- "@pantheon-systems/puck-css": "^0.13.0",
23
+ "@pantheon-systems/puck-css": "^0.14.0",
24
24
  "@puckeditor/core": "^0.21.1",
25
25
  "@tailwindcss/postcss": "^4.2.2",
26
26
  "@tailwindcss/typography": "^0.5.16",
@@ -1,30 +1,27 @@
1
- import { resolve } from "path";
2
1
  import { defineConfig } from "vitest/config";
3
2
  import react from "@vitejs/plugin-react";
4
3
 
5
4
  export default defineConfig({
6
5
  plugins: [react()],
7
- resolve: {
8
- alias: {
9
- "@pantheon-systems/puck-css/fields": resolve(
10
- __dirname,
11
- "../..",
12
- "packages/puck-css/src/data/fields.tsx",
13
- ),
14
- "@pantheon-systems/pds-toolkit-react": resolve(
15
- __dirname,
16
- "../..",
17
- "packages/puck-css/src/__mocks__/@pantheon-systems/pds-toolkit-react.ts",
18
- ),
19
- "@puckeditor/core": resolve(
20
- __dirname,
21
- "../..",
22
- "packages/puck-css/src/__mocks__/@puckeditor/core.ts",
23
- ),
24
- },
25
- },
26
6
  test: {
27
7
  environment: "node",
8
+ // Inlining below means the first test to import puck-css pays for Vite
9
+ // transforming that whole graph — seconds on a cold machine, and the default
10
+ // 5s timeout fails it on slower CI runners while every later test in the file
11
+ // runs in milliseconds.
12
+ testTimeout: 30_000,
13
+ server: {
14
+ deps: {
15
+ // pds-toolkit-react's entry point imports its own dist/index.css, and
16
+ // puck-css imports pds-toolkit-react. Left external, vitest hands that
17
+ // chain to Node's ESM loader, which has no loader for .css; inlining
18
+ // routes it through Vite instead. Both must be listed — inlining only the
19
+ // leaf leaves puck-css external, and Node then loads the whole subtree
20
+ // without consulting this list. The patterns match a resolved path, so
21
+ // they allow pnpm's `@pantheon-systems+puck-css` directory form too.
22
+ inline: [/@pantheon-systems[/+]puck-css/, /@pantheon-systems[/+]pds-toolkit-react/],
23
+ },
24
+ },
28
25
  typecheck: {
29
26
  tsconfig: "./tsconfig.test.json",
30
27
  },
@@ -1,65 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const { loadPublishedPage, notFound } = vi.hoisted(() => ({
4
- loadPublishedPage: vi.fn(),
5
- notFound: vi.fn(() => {
6
- throw new Error("NEXT_NOT_FOUND");
7
- }),
8
- }));
9
-
10
- vi.mock("@pantheon-systems/p1-next-sdk/server", () => ({
11
- loadPublishedPage,
12
- loadRouteTemplateKeys: vi.fn().mockResolvedValue([]),
13
- createCssQueryFetchers: vi.fn().mockReturnValue([]),
14
- }));
15
-
16
- vi.mock("next/navigation", () => ({ notFound }));
17
-
18
- vi.mock("@pantheon-systems/puck-css/server", () => ({
19
- pagePathFromCatchAllSegments: (segments: string[]) => `/${segments.join("/")}`,
20
- loadRemoteDatasourceContext: vi.fn().mockResolvedValue({}),
21
- extractReferencedDatasourceIds: vi.fn().mockReturnValue([]),
22
- resolveDataTemplates: vi.fn(async (d: unknown) => d),
23
- }));
24
-
25
- vi.mock("../lib/remote-datasource-fetchers", () => ({
26
- REMOTE_DATASOURCE_FETCHERS: [],
27
- }));
28
-
29
- vi.mock("../app/[...puckPath]/client", () => ({ Client: () => null }));
30
- vi.mock("../components/content-unavailable", () => ({
31
- ContentUnavailable: () => null,
32
- }));
33
-
34
- import Page from "../app/[...puckPath]/page";
35
-
36
- const render = (...segments: string[]) =>
37
- Page({ params: Promise.resolve({ puckPath: segments }) });
38
-
39
- describe("catch-all route — missing vs unavailable", () => {
40
- beforeEach(() => vi.clearAllMocks());
41
-
42
- // The route is statically renderable, so a 200 here would write every junk URL
43
- // a crawler probes into the response cache as a successful page.
44
- it("404s a path with no published page", async () => {
45
- loadPublishedPage.mockResolvedValue({ status: "missing" });
46
- await expect(render("nope")).rejects.toThrow("NEXT_NOT_FOUND");
47
- expect(notFound).toHaveBeenCalled();
48
- });
49
-
50
- // 404ing here would deindex a live page over a transient backend blip.
51
- it("does not 404 when the backend is unreachable", async () => {
52
- loadPublishedPage.mockResolvedValue({ status: "unavailable" });
53
- await render("real-page");
54
- expect(notFound).not.toHaveBeenCalled();
55
- });
56
-
57
- it("renders published content", async () => {
58
- loadPublishedPage.mockResolvedValue({
59
- status: "ok",
60
- data: { root: { props: { title: "Hi" } }, content: [] },
61
- });
62
- await render("blog");
63
- expect(notFound).not.toHaveBeenCalled();
64
- });
65
- });