PageSourceSearch

https://www.worldboston.org/assets/admin.settings-CLy9nSBQ.js

js worldboston.org collected 2026-10-03 22:55:29 UTC 16,854 bytes, 1 lines download raw bytes

1import{A as C,B as P,C as A,r as m,z as X,d as _,j as e,L as j,F as E,o as D,e as i,l as c,D as I}from"./index-9wHtJ2OB.js";import{u as R}from"./useMutation-yijpQzc3.js";import{u as N}from"./useServerFn-BKknDOEA.js";const L=C({method:"POST"}).handler(P("fd217f139149e4390515cc95c36401d7c3aad3269edb1dc6d7c74ff68b8e54de")),G=C({method:"POST"}).handler(P("a81ca67713b551cb29f5c9e31696dfdf7e1791ab7cb8b70c7d379fae0bf8c27b")),S="site",b=i().trim().email().max(200).optional().or(c("")),T=D({contact_email:b,contact_phone:i().trim().max(50).optional().or(c("")),contact_address:i().trim().max(500).optional().or(c("")),social_twitter:i().trim().url().max(300).optional().or(c("")),social_linkedin:i().trim().url().max(300).optional().or(c("")),social_instagram:i().trim().url().max(300).optional().or(c("")),social_facebook:i().trim().url().max(300).optional().or(c("")),footer_text:i().trim().max(1e3).optional().or(c("")),donate_url:i().trim().url().max(500).optional().or(c("")),newsletter_provider:i().trim().max(80).optional().or(c("")),route_general:b,route_press:b,route_partnership:b,route_programming:b,route_membership:b,route_delegation:b,ga_measurement_id:i().trim().regex(/^(G-[A-Z0-9]+)?$/i,"Must look like G-XXXXXXXXXX").max(40).optional().or(c("")),homepage_promo_enabled:I().optional(),athletes_sheet_csv_url:i().trim().url().max(600).optional().or(c(""))}),w={contact_email:"",contact_phone:"",contact_address:"",social_twitter:"",social_linkedin:"",social_instagram:"",social_facebook:"",footer_text:"",donate_url:"",newsletter_provider:"",route_general:"",route_press:"",route_partnership:"",route_programming:"",route_membership:"",route_delegation:"",ga_measurement_id:"",homepage_promo_enabled:!0,athletes_sheet_csv_url:""};function W(){const o=A(),[t,a]=m.useState(w),[d,p]=m.useState(null),[u,y]=m.useState(null),{data:v,isLoading:f}=X({queryKey:["admin-site-settings"],queryFn:async()=>{const{data:s,error:x}=await _.from("site_settings").select("value").eq("key",S).maybeSingle();if(x)throw x;return s?.value??w}});m.useEffect(()=>{v&&a({...w,...v})},[v]);const l=R({mutationFn:async s=>{const x=s,{error:k}=await _.from("site_settings").upsert({key:S,value:x},{onConflict:"key"});if(k)throw k},onSuccess:()=>{o.invalidateQueries({queryKey:["admin-site-settings"]}),o.invalidateQueries({queryKey:["site-settings"]}),y(Date.now())},onError:s=>p(s instanceof Error?s.message:"Save failed")}),g=s=>{s.preventDefault(),p(null),y(null);const x=T.safeParse(t);if(!x.success){p(x.error.issues[0]?.message??"Invalid");return}l.mutate(x.data)};return e.jsxs("div",{children:[e.jsx(j,{to:"/admin",className:"text-xs font-bold uppercase tracking-widest text-muted-foreground hover:text-foreground",children:"← Admin"}),e.jsx("h1",{className:"mt-3 text-3xl font-bold tracking-tight",children:"Site settings"}),e.jsx("p",{className:"mt-2 text-sm text-muted-foreground max-w-xl",children:"Site-wide values used in the footer, contact section, and social links."}),e.jsxs("section",{className:"mt-8 space-y-3 max-w-3xl",children:[e.jsx("h2",{className:"font-mono text-[10px] uppercase tracking-[0.3em] text-clay",children:"Activation guides"}),e.jsxs("details",{className:"border border-border p-5",children:[e.jsx("summary",{className:"cursor-pointer text-sm font-bold uppercase tracking-widest text-navy",children:"Turn on email notifications (Resend) — step-by-step"}),e.jsxs("p",{className:"mt-3 text-sm text-muted-foreground",children:["New contact messages, newsletter signups, and donation intents are already saved to the ",e.jsx(j,{to:"/admin/inbox",className:"font-bold underline hover:text-navy",children:"Inbox"}),". Wire up Resend below and emails will start going to the people you've checked off in ",e.jsx(j,{to:"/admin/users",className:"font-bold underline hover:text-navy",children:"Users & notifications"}),"."]}),e.jsxs("ol",{className:"mt-4 space-y-3 text-sm leading-relaxed list-decimal pl-5",children:[e.jsxs("li",{children:["Create a free account at ",e.jsx("a",{href:"https://resend.com",target:"_blank",rel:"noreferrer",className:"font-bold underline hover:text-navy",children:"resend.com"}),". Generous free tier, clean interface."]}),e.jsxs("li",{children:["Verify ",e.jsx("code",{className:"bg-muted px-1",children:"worldboston.org"}
1)," as a sending domain — Resend walks you through DNS records (",e.jsx("a",{href:"https://resend.com/docs/dashboard/domains/introduction",target:"_blank",rel:"noreferrer",className:"font-bold underline hover:text-navy",children:"guide"}),")."]}),e.jsxs("li",{children:["In Resend → API Keys, create a new key (starts with ",e.jsx("code",{children:"re_"}),")."]}),e.jsxs("li",{children:["In Lovable → Project Settings → Secrets, add a secret named exactly ",e.jsx("code",{className:"bg-muted px-1",children:"RESEND_API_KEY"})," with that value."]}),e.jsx("li",{children:"Ping your developer to flip the feature flag. Emails start flowing."})]}),e.jsx("p",{className:"mt-4 text-xs text-muted-foreground",children:"Prefer Postmark / SendGrid / Mailgun? Setup is almost identical — your developer swaps one fetch call."})]}),e.jsxs("details",{className:"border border-border p-5",children:[e.jsx("summary",{className:"cursor-pointer text-sm font-bold uppercase tracking-widest text-navy",children:"Turn on online donations — step-by-step"}),e.jsxs("p",{className:"mt-3 text-sm text-muted-foreground",children:["The donate form on Get Involved already captures donor name, email, amount, and an optional note (saved to ",e.jsx(j,{to:"/admin/inbox",search:{tab:"donations"},className:"font-bold underline hover:text-navy",children:"Inbox → Donations"}),"). It just doesn't charge the card yet."]}),e.jsxs("ol",{className:"mt-4 space-y-3 text-sm leading-relaxed list-decimal pl-5",children:[e.jsxs("li",{children:[e.jsx("strong",{children:"Pick a platform."})," Default: ",e.jsx("a",{href:"https://stripe.com",target:"_blank",rel:"noreferrer",className:"font-bold underline hover:text-navy",children:"Stripe"}),". Nonprofit-friendly alternatives: ",e.jsx("a",{href:"https://givebutter.com",target:"_blank",rel:"noreferrer",className:"font-bold underline hover:text-navy",children:"Givebutter"}),", ",e.jsx("a",{href:"https://donorbox.org",target:"_blank",rel:"noreferrer",className:"font-bold underline hover:text-navy",children:"Donorbox"}),", ",e.jsx("a",{href:"https://www.classy.org",target:"_blank",rel:"noreferrer",className:"font-bold underline hover:text-navy",children:"Classy"}),"."]}),e.jsxs("li",{children:[e.jsx("strong",{children:"If Stripe:"})," create an account, activate with EIN + bank info, then Developers → API Keys → reveal the live secret key. In Lovable → Project Settings → Secrets, add ",e.jsx("code",{className:"bg-muted px-1",children:"STRIPE_SECRET_KEY"}),"."]}),e.jsxs("li",{children:[e.jsx("strong",{children:"If a different platform:"})," just paste the platform's donate URL in the Donations section below. The site's Donate button will link straight there — no developer work needed."]}),e.jsx("li",{children:"For Stripe: ping your developer to flip the feature flag. Online checkout goes live."})]})]})]}),f?e.jsx("p",{className:"mt-8 text-sm text-muted-foreground",children:"Loading…"}):e.jsxs("form",{onSubmit:g,className:"mt-8 space-y-6 max-w-2xl",children:[e.jsxs(h,{title:"Contact",children:[e.jsx(n,{label:"Email",children:e.jsx("input",{type:"email",value:t.contact_email,onChange:s=>a({...t,contact_email:s.target.value}),className:r})}),e.jsx(n,{label:"Phone",children:e.jsx("input",{type:"text",value:t.contact_phone,onChange:s=>a({...t,contact_phone:s.target.value}),className:r})}),e.jsx(n,{label:"Address",children:e.jsx("textarea",{value:t.contact_address,onChange:s=>a({...t,contact_address:s.target.value}),rows:3,className:r})})]}),e.jsxs(h,{title:"Social",children:[e.jsx(n,{label:"Twitter / X URL",children:e.jsx("input",{type:"url",value:t.social_twitter,onChange:s=>a({...t,social_twitter:s.target.value}),className:r})}),e.jsx(n,{label:"LinkedIn URL",children:e.jsx("input",{type:"url",value:t.social_linkedin,onChange:s=>a({...t,social_linkedin:s.target.value}),className:r})}),e.jsx(n,{label:"Instagram URL",children:e.jsx("input",{type:"url",value:t.social_instagram,onChange:s=>a({...t,social_instagram:s.target.value}),className:r})}),e.jsx(n,{label:"Facebook URL",children:e.jsx("input",{type:"url",value:t.social_facebook,onChange:s=>a({...t,social_facebook:s.target.value}),className:r})})]}),e.jsxs(h,{title:"Contact form routing",children:[e.jsx("p",{className:"text-xs text-muted-foreground -mt-1",children:"Where each inquiry type from the contact form should be sent. Leave blank to fall back to the main Contact email above. (Email delivery to these inboxes ships in a later step; Salesforce sync is v2.)"}),e.jsx(n,{label:"General inquiries",children:e.jsx("input",{type:"email",placeholder:"[email protected]",value:t.route_general,onChange:s=>a({...t,route_general:s.target.value}),className:r})}),e.jsx(n,{label:"Press",children:e.jsx("input",{type:"email",value:t.route_press,onChange:s=>a({...t,route_press:s.target.value}),className:r})}),e.jsx(n,{label:"Partnerships",children:e.jsx("input",{type:"email",value:t.route_partnership,onChange:s=>a({...t,route_partnership:s.target.value}),className:r})}),e.jsx(n,{label:"Programming",children:e.jsx("input",{type:"email",value:t.route_programming,onChange:s=>a({...t,route_programming:s.target.value}),className:r})}),e.jsx(n,{label:"Membership",children:e.jsx("input",{type:"email",value:t.route_membership,onChange:s=>a({...t,route_membership:s.target.value}),className:r})}),e.jsx(n,{label:"Delegations / IVLP",children:e.jsx("input",{type:"email",value:t.route_delegation,onChange:s=>a({...t,route_delegation:s.target.value}),className:r})})]}),e.jsxs(h,{title:"Donations",children:[e.jsx("p",{className:"text-xs text-muted-foreground -mt-1",children:"Paste the URL of your donations platform (Classy, Givebutter, Donorbox, etc.). When set, the Donate button in the site nav links out to it in a new tab. Leave blank to hide the button."}),e.jsx(n,{label:"Donate URL",children:e.jsx("input",{type:"url",placeholder:"https://givebutter.com/worldboston",value:t.donate_url,onChange:s=>a({...t,donate_url:s.target.value}),className:r})})]}),e.jsxs(h,{title:"Newsletter",children:[e.jsx("p",{className:"text-xs text-muted-foreground -mt-1",children:"All signups are stored in the Subscribers list and can be exported to CSV. Pick a provider so the team knows where this list eventually syncs; integration ships when the choice is final."}),e.jsx(n,{label:"Planned provider",children:e.jsxs("select",{value:t.newsletter_provider,onCh
1ange:s=>a({...t,newsletter_provider:s.target.value}),className:r,children:[e.jsx("option",{value:"",children:"— Not chosen yet —"}),e.jsx("option",{value:"mailchimp",children:"Mailchimp"}),e.jsx("option",{value:"constant_contact",children:"Constant Contact"}),e.jsx("option",{value:"salesforce",children:"Salesforce Marketing Cloud"}),e.jsx("option",{value:"db_only",children:"Keep in WorldBoston DB only"})]})})]}),e.jsxs(h,{title:"Analytics",children:[e.jsxs("p",{className:"text-xs text-muted-foreground -mt-1",children:["Paste your ",e.jsx("strong",{children:"Google Analytics 4 Measurement ID"})," (looks like ",e.jsx("code",{children:"G-XXXXXXXXXX"}),"). Find it at analytics.google.com → Admin → Data Streams → Web → copy the Measurement ID. Leave blank to disable tracking entirely — no GA scripts will load."]}),e.jsx(n,{label:"GA4 Measurement ID",children:e.jsx("input",{type:"text",placeholder:"G-XXXXXXXXXX",value:t.ga_measurement_id,onChange:s=>a({...t,ga_measurement_id:s.target.value}),className:r})})]}),e.jsxs(h,{title:"Student-athlete directory",children:[e.jsxs("p",{className:"text-xs text-muted-foreground -mt-1",children:["Powers the private Consular Corps directory at ",e.jsx("code",{children:"/NCAA"}),". Paste the regular Google Sheets link (the app will pull the CSV export automatically). The sheet must be shared with ",e.jsx("strong",{children:"anyone with the link can view"})," or published to the web. Edits in the sheet appear on the site within about five minutes."]}),e.jsx(n,{label:"Google Sheet link",children:e.jsx("input",{type:"url",placeholder:"https://docs.google.com/spreadsheets/d/.../edit",value:t.athletes_sheet_csv_url,onChange:s=>a({...t,athletes_sheet_csv_url:s.target.value}),className:r})}),e.jsx(F,{}),e.jsx(q,{})]}),e.jsxs(h,{title:"Homepage promo module",children:[e.jsxs("p",{className:"text-xs text-muted-foreground -mt-1",children:["Shows a promo banner on the homepage that links to the Press page. It pulls promo content from the press item marked to override, or the newest published item if none is set. Edit the promo copy on each entry in ",e.jsx(j,{to:"/admin/press",className:"font-bold underline hover:text-navy",children:"Press"}),"."]}),e.jsxs("label",{className:"flex items-center gap-3 text-sm",children:[e.jsx("input",{type:"checkbox",checked:t.homepage_promo_enabled??!0,onChange:s=>a({...t,homepage_promo_enabled:s.target.checked}),className:"h-4 w-4"}),"Show the promo module on the homepage"]})]}),e.jsx(h,{title:"Footer",children:e.jsx(n,{label:"Footer text",children:e.jsx("textarea",{value:t.footer_text,onChange:s=>a({...t,footer_text:s.target.value}),rows:3,className:r})})}),d&&e.jsx("p",{className:"text-sm text-red-600",children:d}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("button",{type:"submit",disabled:l.isPending,className:"px-5 py-3 bg-navy text-primary-foreground text-xs font-bold uppercase tracking-widest hover:bg-forest disabled:opacity-60",children:l.isPending?"Saving…":"Save settings"}),u&&!l.isPending&&e.jsx("span",{className:"text-xs text-forest font-bold uppercase tracking-widest",children:"Saved ✓"})]})]})]})}const r="w-full bg-background border border-border px-3 py-2 text-sm outline-none focus:border-navy";function h({title:o,children:t}){return e.jsxs("fieldset",{className:"border border-border p-5 space-y-4",children:[e.jsx("legend",{className:"px-2 text-[10px] font-bold uppercase tracking-[0.3em] text-clay",children:o}),t]})}function n({label:o,children:t}){return e.jsxs("div",{children:[e.jsx("label",{className:"block text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-2",children:o}),t]})}function F(){const[o,t]=m.useState(""),[a,d]=m.useState("idle"),[p,u]=m.useState(""),y=N(L),v=N(G),{data:f}=X({queryKey:["admin-athletes-password"],queryFn:async()=>{const{data:l}=await _.auth.getSession(),g=l.session?.access_token;if(!g)throw new Error("Not signed in");return y({data:{accessToken:g}})}});return m.useEffect(()=>{f&&t(f.password)},[f]),e.jsxs(n,{label:"Access word",children:[e.jsx("input",{type:"text",placeholder:"bostonglobal",value:o,onChange:l=>{t(l.target.value),d("idle")},className:r}),e.jsxs("div",{className:"mt-2 flex items-center gap-3",children:[e.jsx("button",{type:"button",disabled:a==="saving"||!o.trim(),onClick:async()=>{d("saving"),u("");try{const{data:l}=await _.auth.getSession(),g=l.session?.access_token;if(!g)throw new Error("Not signed in");await v({data:{accessToken:g,password:o.trim()}}),d("saved")}catch(l){u(l instanceof Error?l.message:"Save failed"),d("error")}},className:"border border-navy px-3 py-1.5 text-xs font-bold uppercase tracking-widest text-navy hover:bg-navy hover:text-white disabled:opacity-40",children:a==="saving"?"Saving…":"Save access word"}),a==="saved"&&e.jsx("span",{className:"text-xs text-muted-foreground",children:"Saved."}),a==="error"&&e.jsx("span",{className:"text-xs text-red-600",children:p})]}),e.jsx("p",{className:"mt-1 text-xs text-muted-foreground",children:"Consulates type this one-word password to unlock the directory. Case-insensitive. Saved separately from the settings above so it is never visible to site visitors."})]})}function q(){const[o,t]=m.useState("idle"),[a,d]=m.useState(""),p=N(E);return e.jsxs("div",{className:"border-t border-border pt-4",children:[e.jsx("button",{type:"button",disabled:o==="working",onClick:async()=>{t("working");try{const u=await p();u.status==="ok"?(d(`${u.result.schoolsChecked} rosters checked — ${u.result.added} added, ${u.result.updated} updated, ${u.result.departed} departed.`),t("done")):(d(u.message),t("error"))}catch{d(""),t("error")}},className:"border border-navy px-4 py-2 text-xs font-bold uppercase tracking-[0.2em] text-navy transition hover:bg-navy hover:text-white disabled:opacity-50",children:o==="working"?"Refreshing…":"Refresh directory now"}),e.jsx("p",{className:"mt-2 text-xs text-muted-foreground",children:o==="done"?a||"Refresh complete — reload /NCAA to see the latest data.":o==="error"?a||"Couldn't refresh. Try again in a few minutes.":"Rosters refresh automatically every two months. Use this to check them right now."})]})}
1export{W as component};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.