create-nextblock 0.12.13 → 0.12.15
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 +1 -1
- package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +0 -13
- package/templates/nextblock-template/app/(auth-pages)/sign-up/SignUpForm.tsx +0 -14
- package/templates/nextblock-template/app/[slug]/page.tsx +12 -4
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +209 -101
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +2 -3
- package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +1 -0
- package/templates/nextblock-template/app/cms/interactions/InteractionsModerationClient.tsx +1 -1
- package/templates/nextblock-template/app/cms/interactions/page.tsx +1 -1
- package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +38 -35
- package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +14 -11
- package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +62 -14
- package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +2 -2
- package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
- package/templates/nextblock-template/app/cms/revisions/actions.ts +5 -5
- package/templates/nextblock-template/app/cms/settings/languages/actions.ts +53 -1
- package/templates/nextblock-template/app/cms/settings/languages/components/LanguageDetectionPanel.tsx +188 -0
- package/templates/nextblock-template/app/cms/settings/languages/page.tsx +12 -1
- package/templates/nextblock-template/app/cms/users/actions.ts +0 -3
- package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +0 -2
- package/templates/nextblock-template/app/cms/users/page.tsx +0 -2
- package/templates/nextblock-template/app/layout.tsx +42 -1
- package/templates/nextblock-template/app/product/[slug]/page.tsx +12 -4
- package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +1 -1
- package/templates/nextblock-template/app/profile/account-data.ts +1 -1
- package/templates/nextblock-template/app/profile/account-types.ts +0 -1
- package/templates/nextblock-template/app/profile/page.tsx +0 -1
- package/templates/nextblock-template/app/providers.tsx +2 -0
- package/templates/nextblock-template/components/PostCommentsSection.tsx +2 -2
- package/templates/nextblock-template/components/ProductReviewsSection.tsx +2 -2
- package/templates/nextblock-template/components/header-auth.tsx +1 -1
- package/templates/nextblock-template/context/LanguageContext.tsx +22 -7
- package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +46 -43
- package/templates/nextblock-template/lib/custom-block-relation-registry.ts +3 -3
- package/templates/nextblock-template/lib/i18n/country-languages.ts +247 -0
- package/templates/nextblock-template/lib/i18n/detection.test.ts +197 -0
- package/templates/nextblock-template/lib/i18n/detection.ts +192 -0
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +72 -37
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/proxy.ts +141 -8
- package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
- package/templates/nextblock-template/components/GitHubLoginButton.tsx +0 -36
package/package.json
CHANGED
|
@@ -26,8 +26,6 @@ function getMessage(searchParams: URLSearchParams): Message | undefined {
|
|
|
26
26
|
return undefined;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
import { GitHubLoginButton } from "../../../components/GitHubLoginButton";
|
|
30
|
-
|
|
31
29
|
export default function Login() {
|
|
32
30
|
const { t } = useTranslations();
|
|
33
31
|
const searchParams = useSearchParams();
|
|
@@ -46,17 +44,6 @@ export default function Login() {
|
|
|
46
44
|
</p>
|
|
47
45
|
|
|
48
46
|
<div className="flex flex-col gap-2 mt-8">
|
|
49
|
-
<GitHubLoginButton t={t} redirectTo={redirectParam || undefined} />
|
|
50
|
-
|
|
51
|
-
<div className="relative py-2">
|
|
52
|
-
<div className="absolute inset-0 flex items-center">
|
|
53
|
-
<span className="w-full border-t" />
|
|
54
|
-
</div>
|
|
55
|
-
<div className="relative flex justify-center text-xs uppercase">
|
|
56
|
-
<span className="bg-background px-2 text-muted-foreground">{t('or_continue_with') || "Or continue with"}</span>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
|
|
60
47
|
<form className="flex flex-col gap-2 [&>input]:mb-3">
|
|
61
48
|
{redirectParam && <input type="hidden" name="redirect" value={redirectParam} />}
|
|
62
49
|
<Label htmlFor="email">{t('email')}</Label>
|
|
@@ -7,7 +7,6 @@ import { Button, Input, Label } from "@nextblock-cms/ui";
|
|
|
7
7
|
import Link from "next/link";
|
|
8
8
|
import { useTranslations } from "@nextblock-cms/utils";
|
|
9
9
|
import { useSearchParams } from "next/navigation";
|
|
10
|
-
import { GitHubLoginButton } from "../../../components/GitHubLoginButton";
|
|
11
10
|
import { ArrowRight, CheckCircle2, Mail } from "lucide-react";
|
|
12
11
|
|
|
13
12
|
import { SandboxCredentialsAlert } from "../../../components/SandboxCredentialsAlert";
|
|
@@ -100,19 +99,6 @@ export default function SignUpForm({ botProtection, scriptNonce }: SignUpFormPro
|
|
|
100
99
|
</p>
|
|
101
100
|
|
|
102
101
|
<div className="mt-8 flex flex-col gap-2">
|
|
103
|
-
<GitHubLoginButton t={t} redirectTo="/profile" />
|
|
104
|
-
|
|
105
|
-
<div className="relative py-2">
|
|
106
|
-
<div className="absolute inset-0 flex items-center">
|
|
107
|
-
<span className="w-full border-t" />
|
|
108
|
-
</div>
|
|
109
|
-
<div className="relative flex justify-center text-xs uppercase">
|
|
110
|
-
<span className="bg-background px-2 text-muted-foreground">
|
|
111
|
-
{t('or_continue_with') || "Or continue with"}
|
|
112
|
-
</span>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
102
|
<div className="flex flex-col gap-2 [&>input]:mb-3">
|
|
117
103
|
<Label htmlFor="email">{t('email')}</Label>
|
|
118
104
|
<Input name="email" placeholder={t('you_at_example_com')} required />
|
|
@@ -78,8 +78,12 @@ export async function generateMetadata(
|
|
|
78
78
|
if (!preferredLocale) {
|
|
79
79
|
try {
|
|
80
80
|
const hdrs = await headers();
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// Proxy-detected locale first: it honors the CMS language-detection settings.
|
|
82
|
+
preferredLocale = hdrs.get("x-user-locale") || undefined;
|
|
83
|
+
if (!preferredLocale) {
|
|
84
|
+
const al = hdrs.get("accept-language");
|
|
85
|
+
if (al) preferredLocale = al.split(",")[0]?.split("-")[0];
|
|
86
|
+
}
|
|
83
87
|
} catch {
|
|
84
88
|
// ignore header lookup errors
|
|
85
89
|
}
|
|
@@ -153,8 +157,12 @@ export default async function DynamicPage({ params: paramsPromise }: PageProps)
|
|
|
153
157
|
if (!preferredLocale) {
|
|
154
158
|
try {
|
|
155
159
|
const hdrs = await headers();
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
// Proxy-detected locale first: it honors the CMS language-detection settings.
|
|
161
|
+
preferredLocale = hdrs.get("x-user-locale") || undefined;
|
|
162
|
+
if (!preferredLocale) {
|
|
163
|
+
const al = hdrs.get("accept-language");
|
|
164
|
+
if (al) preferredLocale = al.split(",")[0]?.split("-")[0];
|
|
165
|
+
}
|
|
158
166
|
} catch {
|
|
159
167
|
// ignore header lookup errors
|
|
160
168
|
}
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
// AUTO-GENERATED FILE. Do not edit this file directly.
|
|
2
|
-
// Regenerate it from repo root with: npm run generate:sandbox
|
|
3
|
-
// Source of truth: tools/scripts/generate-sandbox-reset.ts and libs/db/src/supabase/migrations/*.sql
|
|
4
|
-
|
|
5
|
-
export const SANDBOX_RESET_SQL = `
|
|
6
|
-
-- AUTO-GENERATED: Sandbox Reset Script. Do Not Edit Manually.
|
|
7
|
-
-- Generated by tools/scripts/generate-sandbox-reset.ts
|
|
8
|
-
|
|
9
|
-
-- Step A: Erase all non-demo users
|
|
10
|
-
DELETE FROM auth.users
|
|
11
|
-
WHERE id NOT IN (
|
|
12
|
-
'63a8aa07-ef6e-4cc7-bafa-ece023c74366'::uuid,
|
|
13
|
-
'e3a2c121-70a4-4239-a9df-f70ce717f5a1'::uuid
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
-- Step B: Wipe public schema and truncate migrations
|
|
17
|
-
DROP SCHEMA public CASCADE;
|
|
18
|
-
CREATE SCHEMA public;
|
|
19
|
-
|
|
20
|
-
-- Restore default public schema privileges so the standard migrations succeed
|
|
21
|
-
GRANT USAGE ON SCHEMA public TO postgres, anon, authenticated, service_role;
|
|
22
|
-
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO postgres, anon, authenticated, service_role;
|
|
23
|
-
GRANT ALL PRIVILEGES ON ALL ROUTINES IN SCHEMA public TO postgres, anon, authenticated, service_role;
|
|
24
|
-
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO postgres, anon, authenticated, service_role;
|
|
25
|
-
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;
|
|
26
|
-
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON ROUTINES TO postgres, anon, authenticated, service_role;
|
|
27
|
-
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;
|
|
28
|
-
|
|
29
|
-
TRUNCATE TABLE supabase_migrations.schema_migrations;
|
|
30
|
-
|
|
31
|
-
-- Step C: Execute full schema & seed from production migrations
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-- >>> FROM: 00000000000000_baseline_schema.sql <<<
|
|
1
|
+
// AUTO-GENERATED FILE. Do not edit this file directly.
|
|
2
|
+
// Regenerate it from repo root with: npm run generate:sandbox
|
|
3
|
+
// Source of truth: tools/scripts/generate-sandbox-reset.ts and libs/db/src/supabase/migrations/*.sql
|
|
4
|
+
|
|
5
|
+
export const SANDBOX_RESET_SQL = `
|
|
6
|
+
-- AUTO-GENERATED: Sandbox Reset Script. Do Not Edit Manually.
|
|
7
|
+
-- Generated by tools/scripts/generate-sandbox-reset.ts
|
|
8
|
+
|
|
9
|
+
-- Step A: Erase all non-demo users
|
|
10
|
+
DELETE FROM auth.users
|
|
11
|
+
WHERE id NOT IN (
|
|
12
|
+
'63a8aa07-ef6e-4cc7-bafa-ece023c74366'::uuid,
|
|
13
|
+
'e3a2c121-70a4-4239-a9df-f70ce717f5a1'::uuid
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
-- Step B: Wipe public schema and truncate migrations
|
|
17
|
+
DROP SCHEMA public CASCADE;
|
|
18
|
+
CREATE SCHEMA public;
|
|
19
|
+
|
|
20
|
+
-- Restore default public schema privileges so the standard migrations succeed
|
|
21
|
+
GRANT USAGE ON SCHEMA public TO postgres, anon, authenticated, service_role;
|
|
22
|
+
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO postgres, anon, authenticated, service_role;
|
|
23
|
+
GRANT ALL PRIVILEGES ON ALL ROUTINES IN SCHEMA public TO postgres, anon, authenticated, service_role;
|
|
24
|
+
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO postgres, anon, authenticated, service_role;
|
|
25
|
+
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;
|
|
26
|
+
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON ROUTINES TO postgres, anon, authenticated, service_role;
|
|
27
|
+
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;
|
|
28
|
+
|
|
29
|
+
TRUNCATE TABLE supabase_migrations.schema_migrations;
|
|
30
|
+
|
|
31
|
+
-- Step C: Execute full schema & seed from production migrations
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
-- >>> FROM: 00000000000000_baseline_schema.sql <<<
|
|
35
35
|
-- AUTO-GENERATED baseline (re-baseline of migrations 000..044). Idempotent; safe to replay.
|
|
36
36
|
-- 00 · schema: enums, functions, tables, sequences, defaults
|
|
37
37
|
-- Regenerate via tools/scripts/rebaseline-transform.mjs. Do not hand-edit.
|
|
@@ -2486,9 +2486,9 @@ CREATE TABLE IF NOT EXISTS public.variant_attribute_mapping (
|
|
|
2486
2486
|
variant_id uuid NOT NULL,
|
|
2487
2487
|
attribute_term_id uuid NOT NULL
|
|
2488
2488
|
);
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
-- >>> FROM: 00000000000001_baseline_constraints_and_indexes.sql <<<
|
|
2489
|
+
|
|
2490
|
+
|
|
2491
|
+
-- >>> FROM: 00000000000001_baseline_constraints_and_indexes.sql <<<
|
|
2492
2492
|
-- AUTO-GENERATED baseline (re-baseline of migrations 000..044). Idempotent; safe to replay.
|
|
2493
2493
|
-- 01 · constraints (PK / unique / check / FK) + indexes
|
|
2494
2494
|
-- Regenerate via tools/scripts/rebaseline-transform.mjs. Do not hand-edit.
|
|
@@ -3560,9 +3560,9 @@ DO $rb$ BEGIN
|
|
|
3560
3560
|
ADD CONSTRAINT variant_attribute_mapping_variant_id_fkey FOREIGN KEY (variant_id) REFERENCES public.product_variants(id) ON DELETE CASCADE;
|
|
3561
3561
|
END IF;
|
|
3562
3562
|
END $rb$;
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
-- >>> FROM: 00000000000002_baseline_security_and_grants.sql <<<
|
|
3563
|
+
|
|
3564
|
+
|
|
3565
|
+
-- >>> FROM: 00000000000002_baseline_security_and_grants.sql <<<
|
|
3566
3566
|
-- AUTO-GENERATED baseline (re-baseline of migrations 000..044). Idempotent; safe to replay.
|
|
3567
3567
|
-- 02 · row-level security, policies, triggers, grants
|
|
3568
3568
|
-- Regenerate via tools/scripts/rebaseline-transform.mjs. Do not hand-edit.
|
|
@@ -4898,9 +4898,9 @@ CREATE TRIGGER on_auth_user_created
|
|
|
4898
4898
|
FOR EACH ROW
|
|
4899
4899
|
EXECUTE FUNCTION public.handle_new_user();
|
|
4900
4900
|
REVOKE EXECUTE ON FUNCTION public.handle_new_user() FROM PUBLIC, anon, authenticated;
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
-- >>> FROM: 00000000000003_baseline_seed.sql <<<
|
|
4901
|
+
|
|
4902
|
+
|
|
4903
|
+
-- >>> FROM: 00000000000003_baseline_seed.sql <<<
|
|
4904
4904
|
-- AUTO-GENERATED baseline (re-baseline of migrations 000..044). Idempotent; safe to replay.
|
|
4905
4905
|
-- 03 · seed: canonical NextBlock demo content (no users, no secrets)
|
|
4906
4906
|
-- Regenerate via tools/scripts/rebaseline-transform.mjs. Do not hand-edit.
|
|
@@ -5369,9 +5369,9 @@ SELECT pg_catalog.setval('public.languages_id_seq', COALESCE((SELECT MAX(id) FRO
|
|
|
5369
5369
|
SELECT pg_catalog.setval('public.navigation_items_id_seq', COALESCE((SELECT MAX(id) FROM public.navigation_items), 1), true);
|
|
5370
5370
|
SELECT pg_catalog.setval('public.pages_id_seq', COALESCE((SELECT MAX(id) FROM public.pages), 1), true);
|
|
5371
5371
|
SELECT pg_catalog.setval('public.posts_id_seq', COALESCE((SELECT MAX(id) FROM public.posts), 1), true);
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
-- >>> FROM: 00000000000004_default_logo_email_safe.sql <<<
|
|
5372
|
+
|
|
5373
|
+
|
|
5374
|
+
-- >>> FROM: 00000000000004_default_logo_email_safe.sql <<<
|
|
5375
5375
|
-- Swap the seeded DEFAULT site logo from the WebP asset to a bundled PNG.
|
|
5376
5376
|
--
|
|
5377
5377
|
-- WebP (and AVIF) don't render in Outlook and several other email clients, so the default
|
|
@@ -5396,9 +5396,9 @@ UPDATE public.logos
|
|
|
5396
5396
|
SET media_id = 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d'
|
|
5397
5397
|
WHERE id = 'd45ef03d-27fc-4099-8b46-1cdf82d658d2'
|
|
5398
5398
|
AND media_id = 'ea6fdaf5-f8de-416c-9f2b-b689b7a4ed38';
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
-- >>> FROM: 00000000000005_add_custom_canonical.sql <<<
|
|
5399
|
+
|
|
5400
|
+
|
|
5401
|
+
-- >>> FROM: 00000000000005_add_custom_canonical.sql <<<
|
|
5402
5402
|
-- Add a nullable \`custom_canonical\` column to pages, posts and products.
|
|
5403
5403
|
--
|
|
5404
5404
|
-- This is the per-content manual canonical override used by each route's
|
|
@@ -5412,9 +5412,9 @@ WHERE id = 'd45ef03d-27fc-4099-8b46-1cdf82d658d2'
|
|
|
5412
5412
|
ALTER TABLE public.pages ADD COLUMN IF NOT EXISTS custom_canonical text;
|
|
5413
5413
|
ALTER TABLE public.posts ADD COLUMN IF NOT EXISTS custom_canonical text;
|
|
5414
5414
|
ALTER TABLE public.products ADD COLUMN IF NOT EXISTS custom_canonical text;
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
-- >>> FROM: 00000000000006_home_live_demo_promo.sql <<<
|
|
5415
|
+
|
|
5416
|
+
|
|
5417
|
+
-- >>> FROM: 00000000000006_home_live_demo_promo.sql <<<
|
|
5418
5418
|
-- 00000000000006_home_sandbox_promo.sql
|
|
5419
5419
|
-- Adds a "Live Demo" promo section to the English (slug 'home') and French
|
|
5420
5420
|
-- (slug 'accueil') home pages that drives visitors to the public sandbox at
|
|
@@ -5482,9 +5482,9 @@ $fr$::jsonb, 1);
|
|
|
5482
5482
|
END IF;
|
|
5483
5483
|
END
|
|
5484
5484
|
$body$;
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
-- >>> FROM: 00000000000007_home_live_demo_promo_copy_fix.sql <<<
|
|
5485
|
+
|
|
5486
|
+
|
|
5487
|
+
-- >>> FROM: 00000000000007_home_live_demo_promo_copy_fix.sql <<<
|
|
5488
5488
|
-- 00000000000007_home_live_demo_promo_copy_fix.sql
|
|
5489
5489
|
-- Corrects the "Live Demo" promo that migration 006 seeded on the EN (slug 'home')
|
|
5490
5490
|
-- and FR (slug 'accueil') home pages. 006 was already applied to production and the
|
|
@@ -5534,9 +5534,9 @@ $fr$::jsonb,
|
|
|
5534
5534
|
AND content::text LIKE '%nb-sandbox-promo%';
|
|
5535
5535
|
END
|
|
5536
5536
|
$body$;
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
-- >>> FROM: 00000000000008_setup_article_reorder.sql <<<
|
|
5537
|
+
|
|
5538
|
+
|
|
5539
|
+
-- >>> FROM: 00000000000008_setup_article_reorder.sql <<<
|
|
5540
5540
|
-- 00000000000008_setup_article_reorder.sql
|
|
5541
5541
|
-- Reorders and rewrites the "How to Install NextBlock" setup guide on BOTH language
|
|
5542
5542
|
-- twins: EN (post slug 'how-to-setup-nextblock', post_id=3 / block id=40) and FR
|
|
@@ -5567,9 +5567,9 @@ $nbfr$::jsonb,
|
|
|
5567
5567
|
updated_at = now()
|
|
5568
5568
|
WHERE post_id = 4
|
|
5569
5569
|
AND block_type = 'text';
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
-- >>> FROM: 00000000000009_home_live_demo_promo_contrast.sql <<<
|
|
5570
|
+
|
|
5571
|
+
|
|
5572
|
+
-- >>> FROM: 00000000000009_home_live_demo_promo_contrast.sql <<<
|
|
5573
5573
|
-- 00000000000009_home_live_demo_promo_contrast.sql
|
|
5574
5574
|
-- Restyles the "Live Demo" promo that migrations 006/007 seeded on the EN (slug 'home')
|
|
5575
5575
|
-- and FR (slug 'accueil') home pages. 006/007 gave it a fixed dark-navy GRADIENT section
|
|
@@ -5622,43 +5622,151 @@ $fr$::jsonb,
|
|
|
5622
5622
|
AND content::text LIKE '%nb-sandbox-promo%';
|
|
5623
5623
|
END
|
|
5624
5624
|
$body$;
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
UPDATE
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5625
|
+
|
|
5626
|
+
|
|
5627
|
+
-- >>> FROM: 00000000000010_drop_github_username.sql <<<
|
|
5628
|
+
-- Drop the github_username profile column and remove the GitHub-auth artifacts.
|
|
5629
|
+
--
|
|
5630
|
+
-- GitHub OAuth sign-in / account-linking was removed from the app: it cannot be
|
|
5631
|
+
-- provisioned as part of a one-click deployment (each install needs its own GitHub
|
|
5632
|
+
-- OAuth app + client secret + Supabase's manual-linking toggle, none of which can
|
|
5633
|
+
-- ship in a migration). github_username was only ever populated from that OAuth
|
|
5634
|
+
-- flow, so the column and its seed strings are now dead.
|
|
5635
|
+
--
|
|
5636
|
+
-- NOTE: the self-update "Connect GitHub" flow (lib/updates/*, ConnectGitHubButton)
|
|
5637
|
+
-- is a separate feature and is intentionally left untouched — the connect_github
|
|
5638
|
+
-- translation is kept for it.
|
|
5639
|
+
|
|
5640
|
+
-- 1. Rewrite handle_new_user() so new signups no longer read/populate github_username.
|
|
5641
|
+
CREATE OR REPLACE FUNCTION public.handle_new_user() RETURNS trigger
|
|
5642
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
5643
|
+
SET search_path TO 'public'
|
|
5644
|
+
AS $$
|
|
5645
|
+
DECLARE
|
|
5646
|
+
admin_flag_set boolean := false;
|
|
5647
|
+
user_role public.user_role;
|
|
5648
|
+
v_full_name text;
|
|
5649
|
+
v_avatar_url text;
|
|
5650
|
+
BEGIN
|
|
5651
|
+
INSERT INTO public.site_settings (key, value)
|
|
5652
|
+
VALUES ('is_admin_created', 'false'::jsonb)
|
|
5653
|
+
ON CONFLICT (key) DO NOTHING;
|
|
5654
|
+
|
|
5655
|
+
SELECT COALESCE(value::jsonb::boolean, false)
|
|
5656
|
+
INTO admin_flag_set
|
|
5657
|
+
FROM public.site_settings
|
|
5658
|
+
WHERE key = 'is_admin_created'
|
|
5659
|
+
FOR UPDATE;
|
|
5660
|
+
|
|
5661
|
+
IF admin_flag_set = false THEN
|
|
5662
|
+
user_role := 'ADMIN'::public.user_role;
|
|
5663
|
+
|
|
5664
|
+
UPDATE public.site_settings
|
|
5665
|
+
SET value = 'true'::jsonb
|
|
5666
|
+
WHERE key = 'is_admin_created';
|
|
5667
|
+
ELSE
|
|
5668
|
+
user_role := 'USER'::public.user_role;
|
|
5669
|
+
END IF;
|
|
5670
|
+
|
|
5671
|
+
v_full_name := NEW.raw_user_meta_data->>'full_name';
|
|
5672
|
+
v_avatar_url := NEW.raw_user_meta_data->>'avatar_url';
|
|
5673
|
+
|
|
5674
|
+
INSERT INTO public.profiles (
|
|
5675
|
+
id,
|
|
5676
|
+
role,
|
|
5677
|
+
full_name,
|
|
5678
|
+
avatar_url
|
|
5679
|
+
)
|
|
5680
|
+
VALUES (
|
|
5681
|
+
NEW.id,
|
|
5682
|
+
user_role,
|
|
5683
|
+
v_full_name,
|
|
5684
|
+
v_avatar_url
|
|
5685
|
+
)
|
|
5686
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
5687
|
+
full_name = EXCLUDED.full_name,
|
|
5688
|
+
avatar_url = EXCLUDED.avatar_url;
|
|
5689
|
+
|
|
5690
|
+
RETURN NEW;
|
|
5691
|
+
END;
|
|
5692
|
+
$$;
|
|
5693
|
+
|
|
5694
|
+
-- 2. Drop the column now that nothing writes it.
|
|
5695
|
+
ALTER TABLE public.profiles DROP COLUMN IF EXISTS github_username;
|
|
5696
|
+
|
|
5697
|
+
-- 3. Remove the GitHub-auth-only translation strings. connect_github is intentionally
|
|
5698
|
+
-- kept — it is still used by the self-update "Connect GitHub" button.
|
|
5699
|
+
DELETE FROM public.translations
|
|
5700
|
+
WHERE key IN (
|
|
5701
|
+
'continue_with_github',
|
|
5702
|
+
'github_username',
|
|
5703
|
+
'github_username_help',
|
|
5704
|
+
'github_link_failed',
|
|
5705
|
+
'github_connected'
|
|
5706
|
+
);
|
|
5707
|
+
|
|
5708
|
+
|
|
5709
|
+
-- >>> FROM: 00000000000011_language_detection_admin_only.sql <<<
|
|
5710
|
+
-- Restrict writes to the language-detection settings row to ADMIN only.
|
|
5711
|
+
--
|
|
5712
|
+
-- \`site_settings.language_detection_settings\` is a non-sensitive, anon-READABLE
|
|
5713
|
+
-- key (the request proxy reads it with the anon client to pick a visitor's first
|
|
5714
|
+
-- language). The CMS surfaces it under /cms/settings (ADMIN-only) and the server
|
|
5715
|
+
-- action guards with an ADMIN check, but the baseline write policies let ADMIN
|
|
5716
|
+
-- *or* WRITER write any non-sensitive key — so a WRITER could change site-wide
|
|
5717
|
+
-- detection directly via PostgREST. This migration adds the key to the ADMIN-only
|
|
5718
|
+
-- write group (INSERT/UPDATE/DELETE) to match the UI boundary, while leaving the
|
|
5719
|
+
-- SELECT policy untouched so the proxy's anon read keeps working.
|
|
5720
|
+
--
|
|
5721
|
+
-- Forward-only; recreates the three write policies idempotently.
|
|
5722
|
+
|
|
5723
|
+
DROP POLICY IF EXISTS site_settings_insert_policy ON public.site_settings;
|
|
5724
|
+
CREATE POLICY site_settings_insert_policy ON public.site_settings FOR INSERT TO authenticated WITH CHECK ((((key <> ALL (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = ANY (ARRAY['ADMIN'::public.user_role, 'WRITER'::public.user_role]))) OR ((key = ANY (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = 'ADMIN'::public.user_role))));
|
|
5725
|
+
|
|
5726
|
+
DROP POLICY IF EXISTS site_settings_update_policy ON public.site_settings;
|
|
5727
|
+
CREATE POLICY site_settings_update_policy ON public.site_settings FOR UPDATE TO authenticated USING ((((key <> ALL (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = ANY (ARRAY['ADMIN'::public.user_role, 'WRITER'::public.user_role]))) OR ((key = ANY (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = 'ADMIN'::public.user_role)))) WITH CHECK ((((key <> ALL (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = ANY (ARRAY['ADMIN'::public.user_role, 'WRITER'::public.user_role]))) OR ((key = ANY (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = 'ADMIN'::public.user_role))));
|
|
5728
|
+
|
|
5729
|
+
DROP POLICY IF EXISTS site_settings_delete_policy ON public.site_settings;
|
|
5730
|
+
CREATE POLICY site_settings_delete_policy ON public.site_settings FOR DELETE TO authenticated USING ((((key <> ALL (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = ANY (ARRAY['ADMIN'::public.user_role, 'WRITER'::public.user_role]))) OR ((key = ANY (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = 'ADMIN'::public.user_role))));
|
|
5731
|
+
|
|
5732
|
+
|
|
5733
|
+
-- Step D: Record the applied migrations in history (truncated in Step B) so
|
|
5734
|
+
-- \`npm run db:migrate:check\` reports up to date instead of listing every file as pending.
|
|
5735
|
+
INSERT INTO supabase_migrations.schema_migrations (version, name) VALUES
|
|
5736
|
+
('00000000000000', 'baseline_schema'),
|
|
5737
|
+
('00000000000001', 'baseline_constraints_and_indexes'),
|
|
5738
|
+
('00000000000002', 'baseline_security_and_grants'),
|
|
5739
|
+
('00000000000003', 'baseline_seed'),
|
|
5740
|
+
('00000000000004', 'default_logo_email_safe'),
|
|
5741
|
+
('00000000000005', 'add_custom_canonical'),
|
|
5742
|
+
('00000000000006', 'home_live_demo_promo'),
|
|
5743
|
+
('00000000000007', 'home_live_demo_promo_copy_fix'),
|
|
5744
|
+
('00000000000008', 'setup_article_reorder'),
|
|
5745
|
+
('00000000000009', 'home_live_demo_promo_contrast'),
|
|
5746
|
+
('00000000000010', 'drop_github_username'),
|
|
5747
|
+
('00000000000011', 'language_detection_admin_only')
|
|
5748
|
+
ON CONFLICT (version) DO NOTHING;
|
|
5749
|
+
|
|
5750
|
+
-- Step E: Anchor preserved profiles
|
|
5751
|
+
INSERT INTO public.profiles (id, updated_at, full_name, avatar_url, website, role)
|
|
5752
|
+
SELECT preserved_user.id, NULL, NULL, NULL, NULL, 'ADMIN'
|
|
5753
|
+
FROM (
|
|
5754
|
+
VALUES
|
|
5755
|
+
('63a8aa07-ef6e-4cc7-bafa-ece023c74366'::uuid),
|
|
5756
|
+
('e3a2c121-70a4-4239-a9df-f70ce717f5a1'::uuid)
|
|
5757
|
+
) AS preserved_user(id)
|
|
5758
|
+
WHERE EXISTS (SELECT 1 FROM auth.users WHERE id = preserved_user.id)
|
|
5759
|
+
AND NOT EXISTS (SELECT 1 FROM public.profiles WHERE id = preserved_user.id);
|
|
5760
|
+
|
|
5761
|
+
-- Step F: Re-assert the "first admin exists" flag. The sandbox preserves its demo
|
|
5762
|
+
-- ADMIN users across the wipe (Steps A & E), but the reseeded baseline resets
|
|
5763
|
+
-- is_admin_created to 'false' and handle_new_user() only flips it on a NEW auth.users
|
|
5764
|
+
-- INSERT -- which never fires for preserved users. Without this the proxy setup-gate
|
|
5765
|
+
-- funnels ALL sandbox traffic (including this reset cron on its next run) to /setup even
|
|
5766
|
+
-- though admins already exist. Guarded on an ADMIN profile actually being present.
|
|
5767
|
+
UPDATE public.site_settings
|
|
5768
|
+
SET value = 'true'::jsonb
|
|
5769
|
+
WHERE key = 'is_admin_created'
|
|
5770
|
+
AND EXISTS (SELECT 1 FROM public.profiles WHERE role = 'ADMIN'::public.user_role);
|
|
5771
|
+
|
|
5772
|
+
`;
|
|
@@ -191,7 +191,6 @@ export default function CmsClientLayout({
|
|
|
191
191
|
|
|
192
192
|
const getInitials = () => {
|
|
193
193
|
if (profile && profile.full_name) return profile.full_name.split(' ').map((n: string) => n[0]).join('').substring(0,2).toUpperCase();
|
|
194
|
-
if (profile && profile.github_username) return profile.github_username.substring(0,2).toUpperCase();
|
|
195
194
|
if (user && user.email) return user.email.charAt(0).toUpperCase();
|
|
196
195
|
return "U"; // Default fallback
|
|
197
196
|
}
|
|
@@ -470,11 +469,11 @@ export default function CmsClientLayout({
|
|
|
470
469
|
<div className="mt-auto p-3 border-t border-slate-200 dark:border-slate-700/60 shrink-0">
|
|
471
470
|
<div className="flex items-center gap-3">
|
|
472
471
|
<Avatar className="h-10 w-10 border">
|
|
473
|
-
<AvatarImage src={profile?.avatar_url || undefined} alt={profile?.
|
|
472
|
+
<AvatarImage src={profile?.avatar_url || undefined} alt={profile?.full_name || user?.email} />
|
|
474
473
|
<AvatarFallback className="bg-primary/10 text-primary font-medium">{getInitials()}</AvatarFallback>
|
|
475
474
|
</Avatar>
|
|
476
475
|
<div className="flex-1 min-w-0">
|
|
477
|
-
<p className="text-sm font-medium truncate text-slate-700 dark:text-slate-200">{profile?.full_name ||
|
|
476
|
+
<p className="text-sm font-medium truncate text-slate-700 dark:text-slate-200">{profile?.full_name || user?.email}</p>
|
|
478
477
|
<div className="flex items-center gap-1.5">
|
|
479
478
|
<div className={cn("h-2 w-2 rounded-full", getRoleColor())}></div>
|
|
480
479
|
<p className="text-xs text-slate-500 dark:text-slate-400 capitalize">{role}</p>
|
|
@@ -212,7 +212,7 @@ export default function InteractionsModerationClient({
|
|
|
212
212
|
<div className="space-y-4">
|
|
213
213
|
{filteredInteractions.length > 0 ? (
|
|
214
214
|
filteredInteractions.map((item) => {
|
|
215
|
-
const authorName = item.profiles?.full_name ||
|
|
215
|
+
const authorName = item.profiles?.full_name || "Anonymous";
|
|
216
216
|
const dateStr = new Date(item.created_at).toLocaleString();
|
|
217
217
|
const targetName = item.products?.title || item.posts?.title || "Unknown Target";
|
|
218
218
|
const targetUrl = item.product_id
|
|
@@ -158,12 +158,13 @@ export default function MediaPickerDialog({
|
|
|
158
158
|
</DialogTrigger>
|
|
159
159
|
)}
|
|
160
160
|
<DialogContent className="sm:max-w-[650px] md:max-w-[800px] lg:max-w-[1000px] max-h-[90vh] flex flex-col">
|
|
161
|
-
<DialogHeader>
|
|
161
|
+
<DialogHeader className="shrink-0 pb-1">
|
|
162
162
|
<DialogTitle>{title}</DialogTitle>
|
|
163
163
|
</DialogHeader>
|
|
164
164
|
|
|
165
|
-
<div className="
|
|
165
|
+
<div className="flex-1 min-h-0 overflow-y-auto pr-1">
|
|
166
166
|
<MediaUploadForm
|
|
167
|
+
compact
|
|
167
168
|
returnJustData={true}
|
|
168
169
|
defaultFolder={defaultFolder}
|
|
169
170
|
onUploadSuccess={(newMedia) => {
|
|
@@ -171,39 +172,40 @@ export default function MediaPickerDialog({
|
|
|
171
172
|
handleSelect(newMedia);
|
|
172
173
|
}}
|
|
173
174
|
/>
|
|
174
|
-
</div>
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
<div className="flex-grow flex items-center justify-center">
|
|
192
|
-
<p>Loading media...</p>
|
|
193
|
-
</div>
|
|
194
|
-
) : loadError && filtered.length === 0 ? (
|
|
195
|
-
<div className="flex-grow flex flex-col items-center justify-center gap-3 text-center">
|
|
196
|
-
<p className="max-w-sm text-sm text-muted-foreground">{loadError}</p>
|
|
197
|
-
<Button type="button" variant="outline" size="sm" onClick={() => void fetchLibrary()}>
|
|
198
|
-
Retry
|
|
199
|
-
</Button>
|
|
200
|
-
</div>
|
|
201
|
-
) : filtered.length === 0 ? (
|
|
202
|
-
<div className="flex-grow flex items-center justify-center">
|
|
203
|
-
<p>No media found.</p>
|
|
176
|
+
<Separator className="my-3" />
|
|
177
|
+
|
|
178
|
+
<div className="flex flex-col">
|
|
179
|
+
<h3 className="text-base font-medium mb-2 text-center">Or Select from Library</h3>
|
|
180
|
+
<div className="sticky top-0 z-10 -mx-1 mb-2 bg-background px-1 pb-1">
|
|
181
|
+
<div className="relative">
|
|
182
|
+
<Input
|
|
183
|
+
type="search"
|
|
184
|
+
placeholder="Search library..."
|
|
185
|
+
value={searchTerm}
|
|
186
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
187
|
+
className="pl-10"
|
|
188
|
+
/>
|
|
189
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
190
|
+
</div>
|
|
204
191
|
</div>
|
|
205
|
-
|
|
206
|
-
|
|
192
|
+
{isLoading && filtered.length === 0 ? (
|
|
193
|
+
<div className="flex items-center justify-center py-10">
|
|
194
|
+
<p>Loading media...</p>
|
|
195
|
+
</div>
|
|
196
|
+
) : loadError && filtered.length === 0 ? (
|
|
197
|
+
<div className="flex flex-col items-center justify-center gap-3 py-10 text-center">
|
|
198
|
+
<p className="max-w-sm text-sm text-muted-foreground">{loadError}</p>
|
|
199
|
+
<Button type="button" variant="outline" size="sm" onClick={() => void fetchLibrary()}>
|
|
200
|
+
Retry
|
|
201
|
+
</Button>
|
|
202
|
+
</div>
|
|
203
|
+
) : filtered.length === 0 ? (
|
|
204
|
+
<div className="flex items-center justify-center py-10">
|
|
205
|
+
<p>No media found.</p>
|
|
206
|
+
</div>
|
|
207
|
+
) : (
|
|
208
|
+
<div className="flex flex-wrap gap-3 pb-2">
|
|
207
209
|
{filtered.map((media: Media) => {
|
|
208
210
|
const previewPath = resolveMediaPreviewPath(media);
|
|
209
211
|
const previewSrc = previewPath ? resolveMediaPreviewSrc(previewPath) : null;
|
|
@@ -241,8 +243,9 @@ export default function MediaPickerDialog({
|
|
|
241
243
|
</button>
|
|
242
244
|
);
|
|
243
245
|
})}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
246
249
|
</div>
|
|
247
250
|
</DialogContent>
|
|
248
251
|
</Dialog>
|