Releases: EmberlyOSS/Emberly
Releases · EmberlyOSS/Emberly
[2.4.2] - API Keys and Bucket Partner
Added
- User API Key Management System — Users can now create, name, copy, and revoke multiple personal API keys from
/me→ API tab.- New
UserApiKeyPrisma model (ebk_prefix, SHA-256 hashed, max 10 keys per user,lastUsedAttracking). packages/lib/api-keys/index.ts—generateApiKey(),listUserApiKeys(),createUserApiKey()(enforces 10-key cap),revokeUserApiKey().GET /api/profile/api-keys— lists keys (prefix and metadata only, never the hash).POST— creates a key and returns the full raw value exactly once.DELETE /api/profile/api-keys/[keyId]— revokes a key; ownership enforced viauserIdfilter.packages/components/profile/api-keys-panel.tsx— combined panel: legacy upload token (show/hide/refresh) + named key CRUD (create with name, one-time copy flash, revoke per key).ebk_prefix recognized ingetAuthenticatedUser— SHA-256 hash lookup with non-blockinglastUsedAtupdate; falls through cleanly if prefix matches but no record is found.
- New
- Admin System Key Auth Integration — The existing
esk_system key can now authenticate any admin or superadmin route.requireAdmin(),requireSuperAdmin(),requireRole(), andrequirePermission()all accept an optionalreqparameter; when provided, they fall back to system key auth if no session is present.- Synthetic
SYSTEM_KEY_USERconstant (SUPERADMIN role) returned by newgetSystemKeyUser(req?)helper, which reuses the existingisSystemKeyAuth()check. - All existing callers (no
reqargument) are unaffected.
- Partners Carousel — Marketing partner logos rebuilt from a static grid to an infinite auto-scrolling marquee.
- Duplicated partner list drives a seamless CSS loop; pauses on hover.
- Edge fade masks (
mask-imagegradient) soften the left/right boundaries. animate-marqueekeyframe added totailwind.config.ts(translateX(-50%), 30 s linear infinite).
- Vultr Object Storage Admin Panel — Admins can now provision, manage, and sync Vultr Object Storage instances directly from Admin → Storage.
- New
VultrObjectStoragePrisma model trackingvultrId,region,clusterId,s3Hostname, credentials,tier,status,cfHostname, andcfDnsRecordId. packages/lib/vultr/index.ts— new typed Vultr API client:listObjectStorages(),createObjectStorage(),getObjectStorage(),deleteObjectStorage(),listClusters(),listTiers(),createObjectStorageBucket(),deleteObjectStorageBucket().POST /api/admin/storage/vultr— provisions a new instance via Vultr API, saves to DB, auto-creates a Cloudflare CNAME record.GET /api/admin/storage/vultr— lists all tracked instances; syncs stalestatusand credentials back to DB on every fetch.GET/DELETE /api/admin/storage/vultr/[id]— fetches/deletes a single instance; DELETE removes the Vultr instance, CF DNS record, and DB row.POST /api/admin/storage/vultr/sync— imports new Vultr instances not yet in the DB and updates status + credentials for existing stale records; response includesimported,updated, andskippedcounts.packages/components/admin/settings/vultr-instance-manager.tsx— full admin UI: cluster/tier picker, region selector with SVG country flags, instance list with live status badges, delete with toast confirmation.- Prisma migrations:
20260414071251_add_block_storage_management(VultrObjectStorage table, StorageBucket Vultr fields),20260414084547_add_cf_hostname_to_vultr_storage(cfHostname, cfDnsRecordId columns).
- New
- Cloudflare DNS Auto-Provisioning — When an admin provisions a Vultr Object Storage instance, a DNS-only CNAME record is automatically created in Cloudflare pointing at the Vultr S3 hostname.
createDnsRecord()anddeleteDnsRecord()added topackages/lib/cloudflare/client.ts; record ID stored ascfDnsRecordIdon the instance for reliable cleanup on delete.- Record is DNS-only (
proxied: false) since Vultr TLS certificates only cover*.vultrobjects.com; Cloudflare proxying would break S3 signature authentication.
- Automatic Storage Bucket Provisioning — Purchasing a storage-bucket subscription now automatically creates a dedicated Vultr bucket for the user with no manual admin intervention required.
- Stripe
checkout.session.completedwebhook readsmetadata.type === 'storage-bucket'andmetadata.locationto select the correct Vultr pool and callcreateObjectStorageBucket(). - On subscription cancellation, the webhook calls
deleteObjectStorageBucket(), clears theStorageBucketrecord, and unlinks the user — keeping costs proportional to active subscribers. - Per-user bucket name is DNS-safe and scoped to the user ID (
emberly-{userId slice}). user.bucket-provisionedanduser.bucket-deprovisionedevents wired into email and Discord notification preference maps.
- Stripe
- Tiered Storage Bucket Products — The S3/Object Storage tab on the pricing page now supports five tiers: Standard, Archival, Premium, Performance, and Accelerated.
- Pricing page queries
VultrObjectStoragefor active instances, groups bytierkeyword, and passes per-tieravailableRegionstoS3Section. StorageTiertype exported fromS3Section.tsxcarriesslug,name,priceId,priceCents, andavailableRegions.EXCLUDED_ADDON_KEYSinAddOnsSection.tsxexpanded to cover allstorage-bucket-*slugs so tier products never appear in the generic add-ons list.
- Pricing page queries
- Vultr API Key in Integrations Config —
vultr.apiKeyadded to the config schema (packages/lib/config/index.ts) andDEFAULT_CONFIG; the Vultr client reads from this field with a fallback to theVULTR_API_KEYenvironment variable.
Changed
- Profile API Tab — Upload token and API key management moved out of the Uploads tab into a new dedicated API tab in
/me.KeyRoundicon; tab inserted between Uploads and Applications in the Content group.- Upload Host domain selector remains in the Uploads tab (upload config, not key management).
packages/components/profile/tools/upload-host.tsxextracted as a standalone component and restored toProfileTools.
- S3 Pricing Section Redesigned —
S3Section.tsxcompletely rebuilt around the new tier + region model.- Location picker shows SVG country flags (
country-flag-icons/react/3x2) instead of emoji for consistent cross-platform rendering. - Tier cards replace the old single-product layout; each tier displays its price, region availability, and a "Choose location" picker that is only shown when the admin has provisioned that tier in at least one region.
- Checkout flow passes
metadata.type,metadata.location, andmetadata.tierto the Stripe session so the webhook can auto-provision the correct bucket.
- Location picker shows SVG country flags (
- Dashboard Storage Tracker Reading Correct Source — The overview storage card now reads
user.storageUsed(the denormalized MB field updated on every upload/delete) instead of aggregating raw bytes from theFiletable, which excluded S3-backed objects and caused near-zero or incorrect totals.
Fixed
- Storage Usage Inconsistencies — Several parts of the site were displaying incorrect or near-zero storage values due to unit mismatches across the storage pipeline.
File.sizeandUser.storageUsedare stored in MB; the analytics API was naming those values withbytes-suffixed fields, causing consumers to double-convert.GET /api/analytics/storage— response fields renamed:totalBytes→totalMB,daily[].bytes→daily[].mb,breakdown[].bytes→breakdown[].mb.StorageMetrics.tsx— removed manual/ 1024 / 1024division; now readsdata.totalMBand callsformatFileSize(mb)directly.AnalyticsOverview.tsx—storageTotalBytes→storageTotalMBthroughout;storageDay?.bytes→storageDay?.mb;item.bytes→item.mbin breakdown render.POST /api/files— squadstorageUsedwas being incremented with raw bytes (uploadedFile.size) instead of MB; fixed tobytesToMB(uploadedFile.size).
- Storage Quota Matching All Tiered Bucket Slugs —
getPlanLimits()was only checking for an activestorage-bucketsubscription; users who purchased a tiered variant (e.g.storage-bucket-archival) still had quota enforced. Fixed to match any slug thatstartsWith('storage-bucket'). - File Privacy: Direct URLs Never Expose Storage Hostnames — The
/directendpoint for raw file access and bothGET/POSTvariants of the download route were returning pre-signed Vultr URLs or redirect responses that exposed*.vultrobjects.comhostnames to clients.- All paths now route through Emberly's own proxy (
buildRawUrl()); storage provider URLs are never returned to the browser.
- All paths now route through Emberly's own proxy (
- Admin Vultr Sync Updates Existing Instances — The "Sync Vultr" button previously only imported instances not yet tracked in the database; instances that had drifted (status changed from
pending→active, or credentials populated after instance activation) were skipped silently.- Sync endpoint now splits results into
toImport(new) andtoUpdate(existing with stale status or blank credentials) and runs both in parallel; toast showsimported,updated, andskippedcounts.
- Sync endpoint now splits results into
- Admin Vultr Status Not Synced Back to DB — Vultr instances provisioned while still
pendingwere never updated toactivein the database, causing the pricing page to show no available regions.- Both the list and single-instance GET handlers now detect drift (live status ≠ DB status, or blank credentials now populated) and write the updated values back to the database; list endpoint fires-and-forgets, single-instance endpoint awaits before responding.
- Destructive Confirmations Use Toast Actions — All destructive confirmation dialogs across the admin and profile UI previously used the browser's native
confirm(), which blocks t...
[2.4.1] - Files, Forms & Flexibility
Added
- Static Web Standards Files —
robots.txtandmanifest.webmanifestmoved topublic/for faster serving and simpler deployment.public/robots.txt— Static robot exclusion file for search engines and web crawlers; disallows/api/,/dashboard/,/admin/paths globally and blocks AI crawlers from user content (/u/).public/manifest.webmanifest— Static PWA manifest with standalone display, dark theme (#09090bbackground,#F97316accent), app shortcuts (Upload, Shorten URL, Pricing), and maskable SVG icon support.- No route handlers (
app/robots.ts,app/manifest.tsremoved); Next.js serves frompublic/directly and links manifest from metadata.
- CORS Headers for File Embedding — Emberly-hosted files and images can now be embedded and used externally on other websites.
- New
packages/lib/api/cors.tsmodule providesaddCORSHeaders(),addSecurityHeaders(),handleCORSPreflight(), andgetCORSHeaders()utilities. - File serving routes now respond with
Access-Control-Allow-Origin: *on both requests and preflight OPTIONS, enabling cross-origin file access from any domain. - Response headers include
X-Content-Type-Options: nosniff(prevents MIME-type sniffing) andReferrer-Policy: no-referrer-when-downgrade(enables safe cross-origin embedding). Content-Disposition: inline(notattachment) allows browsers to display files/images inline instead of forcing downloads, enabling embedding in<img>,<video>,<canvas>,<iframe>, and XMLHttpRequest contexts.- Files remain correctly permission-checked server-side; CORS headers simply allow the browser to fetch them cross-origin once access is confirmed.
- Updated routes:
/api/files/[...path]/(raw file serving),/api/files/[id]/download/,/api/files/[id]/thumbnail/,/api/avatars/[filename]/.
- New
- SMTP Email Provider Support — The email system now supports SMTP as an alternative to Resend, selectable via Admin → Settings → Integrations.
emailProvider('resend'|'smtp', default'resend') andsmtp(host,port,secure,user,password,from) added to the config schema andDEFAULT_CONFIGinpackages/lib/config/index.ts.packages/lib/emails/index.ts— addedgetSmtpTransport()backed by nodemailer with connection caching;sendEmail()now branches onintegrations.emailProvider: SMTP path renders React templates to HTML via@react-email/renderand sends via nodemailer; Resend path unchanged.- Admin settings UI (
packages/components/admin/settings/settings-manager.tsx) — Email Provider selector (Resend / SMTP) added to the Resend section; new SMTP section with Host, Port, Secure (TLS) toggle, Username, Password, and From Address fields plus a Test Connection button. app/api/admin/integrations/test/route.ts— addedtestSmtp()usingnodemailer.createTransport().verify()and wired'smtp'into theIntegrationKeyunion and switch.
- File Sharing & Collaborators — Files can now be shared with other users directly from the file manager.
app/api/files/[id]/collaborators/route.ts— newGET/POST/PATCH/DELETEendpoints for managing per-file collaborators with role support (viewer/editor).- Share button added to every
FileCard; opens a dialog showing current collaborators (with role badges and remove controls) and an invite form (email + role). FileSharedEmailtemplate (packages/lib/emails/templates/file-shared.tsx) — notifies the recipient with file name, sharer name, and a direct link.- Squad files are now correctly scoped:
GET /api/filesfilters bysquadIdwhen a query param is provided, with membership verification.
- Dashboard Tabbed Navigation —
DashboardShell(packages/components/dashboard/dashboard-shell.tsx) rewritten from a sidebar layout to a horizontal pill tab strip.- Tabs: Overview, Files, Upload, Paste, Links, Domains, Analytics, Buckets, Discovery.
- Active tab highlighted with
bg-primary/10 text-primary border border-primary/20; strip is horizontally scrollable on mobile inside aScrollIndicator. headerprop still accepted and rendered full-width above the tab strip.
- Squads Dashboard Migrated to
/dashboard/squads— The squads section previously embedded in the Discovery dashboard is now a standalone route at/dashboard/squads. - File Grid Owned Hero —
FileGrid(packages/components/dashboard/file-grid/index.tsx) now owns the full glass-card hero — title, description, and view switcher dropdown — eliminating the need for a separate header prop on the files page.ViewModeis a discriminated union:{ type: 'my-files' } | { type: 'shared' } | { type: 'squad'; squadId: string; squadName: string }.- Squad selector dropdown lets users switch between My Files, Shared with Me, and per-squad views.
- Upload & Paste Forms in Glass Cards —
upload-form.tsxandpaste-form.tsxcontent sections wrapped inglass-cardpanels for visual consistency with the rest of the dashboard. - Private Promo Codes — Admins can now mark a Stripe promotion code as private so it is hidden from the public Discounts tab on the pricing page while still being redeemable at checkout.
app/api/admin/promo-codes/route.ts—POSTacceptsisPrivate: boolean; whentrue, setsmetadata: { private: 'true' }on the Stripe promotion code.GETreturnsisPrivatederived from the same metadata field.app/api/payments/promo-codes/route.ts— public endpoint now filters out any code whose Stripemetadata.privateequals'true'before returning results.packages/components/admin/payments/promo-codes-manager.tsx— "Private code" toggle (Switch + description) added to the create dialog; private codes display anEyeOff · Privatepill badge inline with their code in the table. Create form default state extended withisPrivate: false.
Fixed
- Admin Broadcast Email Markdown Not Rendering — The compose UI advertised markdown support but the
AdminBroadcastEmailtemplate rendered the body as a plain text node, causing**bold**,# headings,- lists, etc. to appear literally in the email.- Added a
markedrenderer with email-safe inline styles for all elements (paragraphs, headings h1–h3, bold, italic, lists, links, blockquotes, code blocks, inline code, horizontal rules) since email clients strip<style>tags and don't honour Tailwind class names on dynamically injected HTML. - Replaced
<Text>{body}</Text>with<div dangerouslySetInnerHTML={{ __html: marked.parse(body) }} />inpackages/lib/emails/templates/admin-broadcast.tsx.
- Added a
- Admin Product List Redesign —
packages/components/admin/products/ProductManager.tsxproduct list rebuilt to match the v2 glass aesthetic.- New
TypeBadgecomponent with colour-coded variants per type:plan→ blue,addon→ violet,nexium-plan→ orange,one-time→ slate. - Product rows use
glass-subtleborder-divided list (consistent with blog-list and partner-list) replacing the oldglass-cardwrapper. - Action buttons (Stripe, Sync, Edit seed, Delete) are always visible instead of appearing only on hover.
- Removed
scale-75Switch hack; switches render at full size with properaria-label. - Inline skeleton loading rows replace the old full-page spinner, maintaining layout stability during load.
- Stats row shows price as
$X.XX / intervalwith the Stripe product ID in dim 10px mono beneath the feature badge row.
- New
- Legal Article Page Redesign —
packages/components/legal/LegalArticle.tsxrebuilt for a more open, professional reading experience.- Removed inner
max-w-5xlconstraint that was double-constraining content insideDashboardWrapper'smax-w-7xl. - Three-card layout replacing the old heavy border stack:
glass-subtlemeta strip (type + date),glass-subtlearticle body with generoussm:p-10padding, andglass-subtlefooter CTA — all spaced viaspace-y-4. - Meta strip is a compact inline row replacing the large icon header card.
- Sidebar cards switched from manual
relative rounded-xl bg-background/80 backdrop-blur-lg border+ gradient overlay toglass-subtle rounded-xl, consistent with the rest of the admin/dashboard UI. Sidebar column narrowed from 280 px to 260 px.
- Removed inner
- Collaborators Route
reqReference Bug —POSTandPATCHhandlers in the collaborators route incorrectly referencedreqinstead ofrequest, causing runtime errors on every invocation. All handlers rewritten with consistentrequestnaming and duplicate exports removed. - File Filters Accidentally Triggered During Mobile Scroll — On touch devices, scrolling past the filter buttons opened their dropdowns because Radix
DropdownMenu/Popoverfire onpointerup, which coincides with a finger lift ending a scroll gesture.useScrollSafeOpenhook added tofile-filters.tsx— touch-triggered opens are deferred by onerequestAnimationFrame; if the browser firespointercancel(scroll took over) before the frame runs, the pending open is cancelled. Mouse and keyboard interactions open immediately with no change in behaviour.
Policy
- SLA Uptime Commitment Adjusted — Monthly uptime guarantee updated from
99.9%to99.6%to better reflect a sustainable foundation for Emberly's current stage of growth. - Infrastructure Timezone Alignment — Monitoring, maintenance windows, and status reporting moved from UTC to Mountain Standard Time (MST / GMT-7) to align with Emberly's Canada-based operations.
[2.4.0] - Refined Vistas
Added
- Profile Route Moved to
/me— User profile settings previously lived at/dashboard/profile; now a standalone route at/mewith its own layout and sidebar.app/(main)/me/layout.tsx— new layout usingDashboardWrapper(dashboard nav, no footer).app/(main)/me/page.tsx— profile page powered byProfileClientwhich has its own Account / Content / Engagement / Billing tab sidebar.app/(main)/me/logout-button.tsx—LogoutButtonmoved fromdashboard/profile/./dashboard/profileroute removed;/profileredirect page updated to point at/me.- OAuth callback routes (GitHub and Discord link/unlink) updated to redirect to
/meafter completion.
- Full-Width Hero Cards on All Dashboard & Admin Pages — Every dashboard and admin page now renders a full-width glass hero/title card above the
[sidebar | content]flex row, rather than inside it.DashboardShellgains an optionalheader?: React.ReactNodeprop; when provided it renders full-width above the sidebar+content flex row.AdminShellgains the sameheaderprop with identical behaviour.- All dashboard pages (
/dashboard,/dashboard/files,/dashboard/analytics,/dashboard/upload,/dashboard/urls,/dashboard/domains,/dashboard/paste,/dashboard/verification-codes,/dashboard/bucket,/dashboard/discovery) updated to pass their hero asheader. - All admin pages (
/admin,/admin/users,/admin/blog,/admin/legal,/admin/settings,/admin/logs,/admin/email,/admin/testimonials,/admin/partners,/admin/products,/admin/reports,/admin/applications) updated with the same pattern. - Hero glass-cards extracted from client components (
DashboardIndex,AnalyticsOverview,AdminOverviewContent) — server pages own the hero, client components render content only. DashboardShellremoved fromdashboard/layout.tsx; each page wraps individually so the shell appears exactly once per page.
- New Sidebar Components — Dedicated sidebar components for dashboard and admin panels.
packages/components/dashboard/dashboard-sidebar.tsx— collapsible nav with all dashboard routes; Discovery section expands to show Talent Profile / Squads / talent sub-links; mobile horizontal scroll strip.packages/components/admin/admin-sidebar.tsx— admin panel nav with all admin routes and mobile scroll strip.
- Files Page — New dedicated files page at
/dashboard/files.app/(main)/dashboard/files/page.tsx+client.tsx— full file browser with filtering, sorting, and bulk actions.packages/components/dashboard/file-grid/index.tsx— significant improvements to the file grid component.
- Discovery Mobile Tab Navigation — The Discovery page previously relied on the sidebar for section navigation, leaving mobile users with no way to switch between Talent and Squads or navigate talent sub-sections.
NexiumDashboardClientrenders two horizontally-scrollable tab strips on mobile (lg:hidden) when not in squad-detail view.- Top strip: Talent Profile / Squads — controls the
selectedTabstate. - Sub-strip (Talent only): Profile / Skills / Signals / Opportunities / Applications — controls
talentSection. - Both strips match the existing dashboard sidebar mobile style (
glass-subtle rounded-xl p-1.5, active statebg-primary/10 text-primary border border-primary/20).
- Discovery Navigation in Dashboard Sidebar — Discovery sub-links integrated directly into
DashboardSidebaras a collapsible expandable section, replacing the old discovery-specific internal sidebar.- Parent row with
ChevronDowntoggle; children are Talent Profile and Squads. - Talent sub-links (Profile, Skills, Signals, Opportunities, Applications) render as indented secondary items when on a talent sub-page.
- Expand state initialised to open when already under
/dashboard/discovery.
- Parent row with
Fixed
- Double Navbar on
/meRoutes —ConditionalBaseNavexcluded/dashboardand/adminfrom the base nav but not/me, causing two navbars to render on the profile page (<BaseNav />from the main layout +DashboardWrapper's fixed glass navbar).- Added
/meto the exclusion list inconditional-base-nav.tsx.
- Added
LogoutButtonImport Broken After Profile Move —discovery/page.tsximportedLogoutButtonfrom../../profile/logout-buttonwhich no longer exists after the profile route moved to/me. Fixed to../../me/logout-button.NexiumSquad.urlId/avatarUrlInvalid Field References — Prisma threwPrismaClientValidationErroron the squad invite routes becauseurlIdandavatarUrlare not fields onNexiumSquad(the correct fields areslugandlogo).GET /api/discovery/invites— squad select updated:urlId→slug,avatarUrl→logo.GET /api/discovery/invites/[token]/accept— same correction.SquadIncomingInvitetype indashboard/discovery/client.tsxupdated to match.
- Squad Member Invite Flow — Various issues in the invite accept/decline redirect flow corrected.
- Build Errors and GitHub Links — Miscellaneous build failures and broken GitHub repository links resolved.
[2.3.0] - The Discovery Update
Added
- Discovery Dashboard Redesign — Full sidebar-controlled layout replacing the previous flat tab strip.
- Three sidebar modes: talent (sub-tabs), squad detail (squad tabs), and top-level (squads list).
- Talent sub-sections — Profile, Skills, Signals, Opportunities, Applications — rendered as indented sidebar items below "Talent Profile" on desktop; horizontal strip on mobile.
NexiumDashboardnow supports optional controlledactiveSection/onSectionChangeprops so the outer sidebar can drive section navigation without internal state conflicts.- Squad detail view rendered inline via
SquadDashboardClientin embedded mode; tab state owned by the parent discovery client.
- Discovery Page Hero Header — Glass-card header on
/dashboard/discoveryshowing the page title, a short description, quick-action links (View Public Profile, Profile Settings, Applications), and aLogoutButton. - GitHub Repo Picker Improvements — Signal creation flow is now picker-first when GitHub is linked.
- Repo picker shown immediately on "Add signal"; manual form is an explicit secondary option.
- Org switcher replaced with a compact native
<select>dropdown (user shown first, then orgs alphabetically). - Multi-select checkboxes on every repo row; selection highlighted with
border-primary/40 bg-primary/5. - Footer "Add X signals" bulk-submit button appears when at least one repo is selected; POSTs each repo as a separate signal and shows a count toast on completion.
- Info callout explains why repos are shown and lists other signal types (deployed apps, OSS contributions, shipped products, etc.) with a link to the manual form.
- Manual form redesigned with "Browse repos" back button and "Cancel" header; no longer shows an inline GitHub shortcut inside the form body.
- GitHub
read:orgOAuth Scope — GitHub account linking now requestsread:orgin addition topublic_repo,repo, so repositories from organizations the user belongs to are accessible in the picker.- Organization repos included via
affiliation=owner,collaborator,organization_memberon the GitHub repos API call. - "Grant org access" re-auth link in the picker footer and error state for users needing to re-authorize.
- Organization repos included via
- Squad Branding Fields —
NexiumSquadschema extended withtagline(≤120 chars),logo(URL),banner(URL),website,twitter,github, anddiscordsocial/web presence fields. - Squad File Relation —
Filemodel gains a nullablesquadIdforeign key andNexiumSquadrelation (onDelete: SetNull), enabling squad-owned file uploads alongside per-user files. - Discovery Dashboard Nav Link — Dashboard navigation updated to surface
/dashboard/discoverydirectly under the Dashboard menu. - Email Template Expansion — 13 new transactional email templates covering the full account lifecycle.
EmailChangedOldEmail/EmailChangedNewEmail— dual notifications sent to both old and new address when email is changed, including timestamp and change source.ExportRequestedEmail/ExportCompletedEmail— confirmations for data export request and download-ready notifications.DeletionRequestedEmail/DeletionCancelledEmail/AccountDeletedEmail— full account deletion flow emails with cancellation window and final confirmation.SubscriptionCreatedEmail/SubscriptionUpdatedEmail/SubscriptionCancelledEmail— Stripe subscription lifecycle notifications with plan details.PaymentSucceededEmail/PaymentFailedEmail— payment confirmation and failure alerts with invoice context.RefundIssuedEmail— refund confirmation with amount and reason.- All templates barrel-exported from
packages/lib/emails/index.tsand wired into the event handler inpackages/lib/events/handlers/email.ts.
- Email Event Handler Overhaul —
packages/lib/events/handlers/email.tscompletely rewritten.- Each template now has a dedicated dispatch branch instead of falling through to the generic
BasicEmailrenderer. - Existing templates (
welcome,verify-email,magic-link,password-reset,account-change,perk-gained,quota-reached,storage-assigned,new-login,admin-broadcast, Nexium invite/welcome/opportunity, application status/reply, bucket credentials) all converted to typedsendTemplateEmailcalls. - Eliminates the previous "body string split" fallback that caused inconsistent rendering across templates.
- Each template now has a dedicated dispatch branch instead of falling through to the generic
- Squad Create Dialog — Full-featured squad creation dialog replacing the previous single-field inline form.
packages/components/discovery/squad-create-dialog.tsx— React Hook Form + Zod validated dialog with name, description, max size, public toggle, and comma-separated skills fields.- Accepts any trigger element as
childrenso it can be opened from any context.
- Add Member Dialog — Searchable user picker for squad owner to invite members.
packages/components/discovery/add-member-dialog.tsx— live search via/api/users/searchwith debounced input, avatar display, and one-click add.- Shows user name, handle, and email; highlights already-added members with a checkmark badge.
- Squad Members Manager Component — Standalone member list with role and kick controls.
packages/components/discovery/squad-members-manager.tsx— renders member rows with inline role<select>(MEMBER / OBSERVER) and kick button; owner row is non-editable.- Calls
POST /api/discovery/squads/[id]/membersfor both role changes and kicks.
- Squad Settings Form — Full settings and danger-zone panel for squad owners.
packages/components/discovery/squad-settings-form.tsx— editable name, description, max size, public toggle, and skills; separate danger zone card withAlertDialog-gated disband action.- Validates with Zod, shows inline field errors, and navigates away on successful disband.
- User Search API Endpoint —
GET /api/users/search?q=query&limit=10.- Returns
id,name,email,image,urlIdfor matching users; searchesnameandemailcase-insensitively. - Used by
AddMemberDialogfor live member lookups.
- Returns
- Squad Members GET Endpoint —
GET /api/discovery/squads/[id]/membersnow returns the full member list for authenticated squad members (was previously missing entirely). - GitHub-Style Signal Cards — Nexium proof-of-skill signals now render as rich GitHub embed widgets for
GITHUB_REPOtype signals, and branded cards for all other types.- New
packages/components/profile/signal-card.tsx—GitHubRepoCardrenders with GitHub's dark#0d1117surface, blue repo name link, muted description, topic chips, language color dot (30+ language colors from linguist palette), and live ⭐/🍴 counts pulled from metadata. - Generic signals show the user-supplied logo/banner image as a card header, falling back to a colored type abbreviation chip when no image is provided.
SignalCarddispatches to the correct renderer based ontype; exportedSignalCardDatatype consumed by all three display locations.
- New
- Signal Logo / Banner URL — Non-GitHub signals now accept an optional
imageUrlfield for custom logos or banners displayed in the signal card header.imageUrladded toSignalInputSchemainpackages/types/dto/nexium.tsand exposed as a form field in the Nexium dashboard panel.- Field is hidden for
GITHUB_REPOsignals since the owner avatar is sourced automatically from GitHub API metadata.
- Automatic GitHub Repo Metadata Fetch — When a
GITHUB_REPOsignal is created or updated with agithub.comURL, the API now automatically fetches live repository metadata and stores it in the signal'smetadatafield.POST /api/discovery/signals— callsparseGitHubUrl()thengetRepo(owner, repo)before writing to the database; storesfull_name,description,stargazers_count,forks_count,language,topics, andowner.avatar_url.PUT /api/discovery/signals/[id]— re-fetches metadata when the URL field changes on an existingGITHUB_REPOsignal, keeping star/fork counts fresh on edit.
Fixed
- Squad Dashboard Blank Screen — Squad detail page rendered nothing after a previous session changed the GET route to return
{ squad, isOwner }while the client still read the response as the squad object directly.- Reverted
GET /api/discovery/squads/[id]to return the squad directly viaapiResponse(squad); theisOwnerflag is redundant sincepage.tsxpassesroleas a prop from the server.
- Reverted
- Kick Member Failures — Kick action was broken in two independent ways.
- Wrong HTTP method: client called
DELETE /memberswhich invokesleaveSquad()on the caller (owners can't leave, so always errored). Fixed toPOST /memberswith{ userId, kick: true }. - Wrong ID field: client passed
m.user.urlId(human slug) instead of the UUID. Fixed by addingid: truetoSQUAD_INCLUDE's user selects inpackages/lib/nexium/squads.ts.
- Wrong HTTP method: client called
- ShareX Squad Uploads Returning 401 —
POST /api/filesonly checked session and personal upload tokens; squad-issued upload tokens andnsk_API keys were never validated.getSquadFromBearerToken()is now called first; on match, the squad owner's session is used as the acting user.- Squad uploads check
storageQuotaMBseparately from user quota and incrementnexiumSquad.storageUsed(in bytes) in the same DB transaction.
- Overview Stat Counts Always
–— API keys and domain counts on the squad overview tab showed dashes because those resources were only fetched when the user switched to their respective tabs.- Both are now pre-fetched on initial mount alongside the squad load so overview cards populate immediately.
- Members Route Now Handles Four POST Cases —
POST /api/discovery/squads/[id]/memberswas previously only handling the join-self flow.- Rewritten to dispatch on...
[2.2.0] - Infrastructure & Access Control
Added
- Stripe API Clover Compatibility — Updated promo code creation to use new Stripe v2025-11-17.clover API structure.
promotionCodes.createnow usespromotion: { type: 'coupon', coupon: string }wrapper instead of directcoupon: stringparameter.- Applied to three endpoints:
POST /api/admin/promo-codes,GET /api/admin/promo-codes, andPOST /api/payments/promo-codes. - Promo code responses properly expand nested coupon data via
expand: ['data.promotion.coupon']for complete pricing details.
- Promo Code Orphan Prevention — Coupon deletion rollback mechanism when promotion code creation fails.
- After creating a coupon, if
promotionCodes.createfails, the orphaned coupon is automatically deleted to prevent dangling resources. - Reduces Stripe admin cleanup burden and improves data consistency.
- After creating a coupon, if
- Kener Status Page Integration — Real time service status monitoring from Kener instance.
- Maps Kener v4 monitor state field format to aggregated health statuses:
ACTIVE→UP,INACTIVE→DOWN. GET /api/statusreturns aggregated system health from all visible monitors with 60 second cache TTL.- Properly differentiates between Kener workflow state (ACTIVE/INACTIVE) and actual health status (UP/DOWN/DEGRADED).
- Maps Kener v4 monitor state field format to aggregated health statuses:
- Graceful Status Fallback — Status endpoint returns
UNKNOWNstatus instead of 503 when Kener is unreachable.- Improves UX for development environments without configured status page.
- Prevents production status page from failing hard when external monitoring is unavailable.
- User Buckets Storage System — Granular file storage organization per user.
- New
UserBucketmodel with storage quota tracking and access control. - Prisma migration:
20260404075543_add_user_buckets— Database schema for bucket management. - Enables team-based storage organization without requiring squads.
- New
- User Grants & Permissions System — Role-based access control and permission management.
- New
UserGrantmodel and permission system for granular authorization. - Prisma migration:
20260404085324_add_user_grants— Permission tracking and enforcement. - Supports delegation of admin functions without full superadmin access.
- New
- Sentry Error Tracking Integration — Client and server error reporting with
@sentry/nextjs.- Automatic error capture from both browser and server environments.
- Environment-specific configuration via
sentry.client.config.ts,sentry.server.config.ts, andsentry.edge.config.ts. - Sentry source map upload and release tracking for production deployments.
- Admin Applications List Redesign — Comprehensive card based redesign replacing plain table layout.
- Individual glass card rows with user avatars (initials fallback), role badges, and action buttons.
- Header card with live stats pills showing PENDING and REVIEWING application counts via Prisma
groupByaggregation. - Proper empty state with centered icon when no applications exist.
- Admin Applications Detail Redesign — Improved detail page layout with icon headers and organized sections.
- Header card with accent gradient top bar,
ClipboardCheckicon, and back navigation. - Applicant card uses
next/imagewith initial fallback instead of bare<img>tag for proper image optimization. - Section headers use small caps muted styling for visual hierarchy.
- Metadata sidebar displays icon prefixed rows instead of plain key/value pairs for better readability.
- Header card with accent gradient top bar,
- Pricing Discounts Section Redesign — Flat card design removing layered nesting.
- Changed from outer wrapper with inner nested
glass-subtlecards to flatglass-cardper discount. - Matches the design pattern of
AddOnSelectorcomponent.
- Changed from outer wrapper with inner nested
Changed
- Documentation Files Comprehensive Update — Production-ready documentation for open source project.
README.mdexpanded from minimal placeholder to complete project overview including:- Feature breakdown (file storage, domains, verification, teams, applications, admin tools)
- Tech stack reference (Next.js 15, React 19, TypeScript, Tailwind, Stripe, Prisma, PostgreSQL, Kener)
- Quick start setup guide with prerequisites and step-by-step instructions
- Directory structure overview and contribution guidelines
- Support channels (Discord, GitHub Discussions, email)
- License and acknowledgments sections
CONTRIBUTING.mdnormalized for consistency:- Removed hyphens in compound words (
real-time→realtime,longer-form→longer form) - Removed emoji formatting from template text
- Maintained comprehensive developer setup, PR process, and coding standards documentation
- Removed hyphens in compound words (
- Kener Status API Response Mapping — Updated aggregation logic to handle Kener v4 format correctly.
- Recognizes
ACTIVEas enabled monitor state (treated as UP health status). - Differentiates between workflow state and actual health status properly.
- Fallback to
UNKNOWNstatus prevents status page crashes during Kener outages.
- Recognizes
- GitHub Module Export Structure — Fixed module export for better client bundling.
- Removed unused
githubobject export frompackages/lib/github/index.ts. - Clients now import only required functions (
getGitHubUser,getOrgRepos, etc.) instead of entire module.
- Removed unused
- Grant Constants Client Access — Separated client-safe constants from server code.
public-profile.tsxnow importsGRANT_METAandALL_GRANTSdirectly frompackages/lib/grants/constants.- Prevents Prisma (server-only database code) from being bundled into browser JavaScript.
Fixed
- TypeScript Nullish Coalescing Operator Syntax — Resolved Turbopack build error in GitHub utility.
- Fixed operator precedence in
packages/lib/github/index.ts:181by adding parentheses. - Changed:
org ?? integrations.github?.org || process.env.GITHUB_ORG ?? 'EmberlyOSS' - To:
org ?? (integrations.github?.org || process.env.GITHUB_ORG) ?? 'EmberlyOSS'
- Fixed operator precedence in
- Admin Applications Pages File Truncation — Resolved file truncation issues from concurrent editing.
app/(main)/admin/applications/page.tsx— truncated old table code block removed, file normalized to 224 lines.app/(main)/admin/applications/[id]/page.tsx— stray old code after return removed, missing closing brace added, file normalized to 243 lines.
- Review Form Hydration Error — Fixed React hydration mismatch in application review form.
packages/components/admin/applications/review-form.tsx— replaced invalid<Badge>render inside<p>tag with<div>flex container.- Text content wrapped in
<span>elements to maintain layout structure without hydration mismatches.
- Stripe Promo Code Expansion Fields — Public promo code endpoint now correctly expands promotion coupon data.
- Added
expand: ['data.promotion.coupon']topromotionCodes.listcalls for complete coupon details in responses. - Clients can now access coupon discount amounts directly from promo code API responses.
- Added
- Prisma Bundle in Client Components — Removed Node.js database library from browser bundles.
public-profile.tsxclient component no longer imports server-only database utilities.- Reduced bundle size and prevented runtime errors from missing Node.js modules (dns, fs, net, tls) in browser context.
- Debug Console Output Cleanup — Removed temporary logging statements from Kener integration.
- Cleaned up
[KENER]prefixed console logs used during debugging phase.
- Cleaned up
Technical
- Prisma Migrations — Three migrations for storage and permissions infrastructure.
20260404075543_add_user_buckets— User bucket management schema with quota tracking.20260404085324_add_user_grants— User grants and permission system models.
- Status Endpoint Architecture — Kener integration in
packages/lib/kener/index.ts.getKenerStatus()fetches from/api/v4/monitorswith Bearer token authorization.aggregateStatus()intelligently maps monitor states to health statuses with proper fallback logic.- 60 second cache TTL reduces API load while maintaining reasonable freshness.
- Sentry Configuration — Multi-environment error tracking setup.
sentry.client.config.ts— Browser client configuration with sourcemap upload and release tracking.sentry.server.config.ts— Backend API server error capture.sentry.edge.config.ts— Edge runtime (middleware) error handling.- Environment-aware initialization with development/production specific settings.
[2.1.0] - Quality of Life Improvements
Added
- Brand Icon System (
skill-icons.tsx) — New shared utility inpackages/components/profile/mapping 100+ skill name patterns to brand icons.- Uses react-icons/si (Simple Icons SVG) for modern tech: React, Next.js, TypeScript, Docker, Kubernetes, Terraform, GraphQL, Tailwind CSS, Svelte, Angular, Kotlin, Flutter, Rust, Go, and more.
- Uses devicons CSS font for supplemental coverage: Python, Node.js, PHP, Ruby, Java, Swift, Dart, PostgreSQL, MySQL, MongoDB, Redis, GitHub, Firebase, AWS, HTML5, CSS3, Sass, Linux, Ubuntu, Debian, jQuery, npm, Laravel, Django, Meteor, Heroku, Jenkins, Travis CI, and more.
getSkillIcon(name)returns a type-discriminatedsi|dientry;SkillIconcomponent renders SVG or devicons glyph with correct brand color.- Consumed by both
public-profile.tsxandnexium-dashboard.tsx.
- Skill Level Bar — New
SkillLevelBarcomponent replacing verbose badge text with 4 colored dots (blue/green/orange/purple by level). Used in public profile Talent tab and Nexium dashboard Skills panel. - Skills Category Grouping — Skills are now grouped by category with a subtle category header in both the public profile and Nexium dashboard panels.
- Signal Type Icon Chips — Each signal type renders a colored icon chip for quick visual identification. GitHub repo signals use the
SiGithubbrand icon. All signal cards redesigned with bolder title, small uppercase type label, and cleaner layout. - Per-Tier Contributor & Booster Badges — Perk role badges completely redesigned with custom gradient pill styling per tier.
- Contributor: Bronze (amber), Silver (slate/zinc), Gold (yellow), Platinum (cyan/teal), Diamond (sky→violet gradient).
- Booster: Bronze (amber), Silver (slate/zinc), Gold (yellow), Platinum (fuchsia), Diamond (purple→pink gradient).
- Each badge includes a tier icon and a gradient border, replacing the generic flat
<Badge>component.
- Discord Social Link Icon — Profile Discord social link now renders the
SiDiscordbrand icon (#5865F2) instead of the genericMessageCirclelucide icon. - Admin User Verify — New admin action to manually verify a user's account.
POST /api/admin/users/[id]/verify— setsisVerified: true(or toggles it) on the target user.isVerifiedandstorageQuotaMBfields added toUSER_ADMIN_SELECT; active subscription with product details also included.- Verify button in the admin user list shows
BadgeCheckicon; verified users display a blue checkmark next to their name.
- Admin User List — Plan & Storage columns — Each user row now shows:
- A plan badge (with
Zapicon and plan name, or "Free" fallback). - A storage usage bar with color thresholds (orange at 75%, red at 90%).
- A plan badge (with
- Subscription Sync Endpoint — New
POST /api/payments/sync-subscriptionre-syncs the authenticated user's active Stripe subscriptions into the database. Useful when the original checkout webhook was missed or not yet configured. - Storage Quota Auto-Healing —
getPlanLimits()now automatically attempts a one-time Stripe sync when no active subscription is found in the database. A per-user 5-minute TTL cache prevents excessive Stripe calls. - Analytics Plan & Usage Card —
AnalyticsOverviewnow includes a Plan & Usage section showing:- Current plan name and badge.
- Storage progress bar with used/total and percentage.
- Upload size cap and custom domain limit (from plan).
GET /api/analytics/overviewnow returnsquotaInfoandplanInfofields.
- Billing History — Payment Methods & Subscriptions — The billing settings tab now shows:
- All saved payment methods (card brand, last 4 digits, expiry, default indicator).
- All Stripe subscriptions with status badges (Active/Trial/Past Due/Cancelled), renewal date, billing interval, and amount.
- Fetches all payment method types via
stripe.customers.listPaymentMethods(captures Link-attached cards thattype:'card'misses).
Changed
- Public Profile Redesign —
public-profile.tsxfully rewritten with a tabbed layout (Overview, Files, URLs, Contributions, Talent). Contributions tab lazy-loads on first click. - Contributions API Performance —
GET /api/users/[id]/contributionsparallelized withPromise.allSettled— all repo and commit detail fetches now run concurrently, eliminating sequential O(repos × commits) round-trips. - Analytics Gating —
GET /api/analytics/overviewnow enforces plan-based gating ontopFilesandtopUrlsfields (returns empty arrays for free tier) instead of unconditionally sending them. - Domain Slot Counting —
getPurchasedDomainSlots()now counts yearly subscription domain slots alongside legacy one-off purchases using a parallelPromise.allquery. package.jsonCleanup — Removedbunas a runtime dependency; addedreact-iconsanddeviconsas explicit dependencies;tsxadded for script execution.
Fixed
/u/[shortCode]Username Lookup — Short URL redirect now correctly resolves by username/vanity ID instead of raw user ID.- Proxy Custom Domain Root Rewrite — Fixed incorrect rewrite target when a visitor hits
/on a verified custom domain. - Private Profile Handling — Profile page now correctly shows a private state instead of partially rendering when
profileVisibilityis private. - Duplicate Declaration Crash — Removed stale duplicate code block appended after
public-profile.tsxrewrite that caused five symbols to be declared twice. - Analytics
formatBytesUnit Bug —AnalyticsOverviewwas treating MB values as raw bytes; now correctly multiplies by 1 024² before formatting. - Stripe Credit Balance Sign — Billing history was displaying Stripe's negative customer balance (credit) as a negative number; now correctly inverted to show credit as a positive
stripeBalance.
[v2.0.0] - Talent Discovery
Added
- Royal Purple Theme - Signature preset Emberly theme with rich purple color palette.
- Royal Purple (💜) now the default theme for all new installations.
- Comprehensive color configuration with custom hue/saturation/lightness controls.
- Theme preset system expanded with additional base and animated themes.
- Persistent theme selection in user profile persisted to database.
- Discord Webhook Notification System - Complete Discord integration for account and event notifications.
- New
/api/profile/discord-webhook/testendpoint to validate webhook URLs and send test notifications. - Discord notification handler integrated into event system with automatic preference gating.
- User model extended with
discordWebhookUrl,discordNotificationsEnabled, anddiscordPreferencesfields. - Notification preferences UI in profile settings with category toggles: Security, Account, Billing, Marketing, Product Updates.
- Event-driven delivery: billing events, security alerts, and account changes routed to Discord webhooks.
- Prisma migration:
20260330011308_add_discord_webhook_notification_preferences.
- New
- Custom Domain Routing via Proxy - Visitors on custom domains see owner's public profile on root path.
- New
/api/internal/domain-lookupendpoint for secure hostname → profile mapping. - Middleware-level custom domain detection with fallback to normal routing for non-root paths.
- Dynamic profile lookup using vanityId, urlId, and name with public visibility checks.
- Enables white-label-style domains pointing to individual creator profiles.
- New
- Nexium Talent Discovery Platform - New
/nexiumpage introducing talent discovery features coming to Emberly.- Landing page showcasing Nexium's core features: unified profiles, proof-of-skill signals, smart opportunity routing, squad collaboration.
- "How It Works" journey: Show Your Best Work → Prove with Signals → Match with Opportunities → Collaborate Fast.
- Feature cards with animated hover effects and gradient icons.
- FAQ section with 5 key questions about platform scope, audience, and timeline.
- Audience badges: Developers, Creators, Community Managers, Studios.
- Call-to-action buttons linking to registration and GitHub/Discord community channels.
- Nexium Backend Infrastructure - Complete talent platform backend with profiles, skills, signals, opportunities, applications, and squads.
- 8 new Prisma models:
NexiumProfile,NexiumSkill,NexiumSignal,NexiumOpportunity,NexiumApplication,NexiumSquad,NexiumSquadMember,NexiumSquadSubscription,NexiumSquadApiKey. - 8 new enums:
NexiumAvailability,NexiumSkillLevel,NexiumSignalType,NexiumOpportunityType,NexiumOpportunityStatus,NexiumApplicationStatus,NexiumSquadStatus,NexiumSquadRole. NexiumProfileextendsUserwith unique@handle(lowercase, 3–32 chars), title, headline, availability,lookingFortags, timezone, and location.NexiumSkillsupports level (Beginner → Expert), category (13 categories including Frontend, Backend, Game Dev, Data/ML), and sort ordering (max 30 per profile).NexiumSignaltracks proof-of-work artifacts: GitHub repos, deployed apps, open-source contributions, shipped products, certifications, and more (max 20 per profile, with optional verification).NexiumOpportunitysupports Full-Time, Part-Time, Contract, Collab, and Bounty types with budget ranges, deadlines, required skills, and team size.NexiumApplicationworkflow: PENDING → VIEWED → SHORTLISTED → ACCEPTED/REJECTED/WITHDRAWN with one-app-per-opportunity constraint.- Prisma migration:
20260330091056_add_nexium_tables.
- 8 new Prisma models:
- Nexium Library Modules - Full business logic layer across 7 modules in
packages/lib/nexium/.constants.ts: Platform-wide limits (30 skills, 20 signals, 20 squad members, 10 API keys), handle regex, and enum-to-label mappings for all 8 enums.profiles.ts: CRUD + discovery with paginatedlistProfiles()filtering by availability, skill, andlookingFortags. Handle availability check with case-insensitive validation.skills.ts: Add, update, remove, reorder (atomic transaction), and bulk replace for profile skills.signals.ts: Add, update, remove, reorder, and adminverifySignal()for proof-of-work verification.opportunities.ts: CRUD with poster ownership enforcement, paginated listing with type/skill/remote filters.applications.ts: Apply/withdraw for applicants, list/status-update for opportunity posters.squads.ts: Full squad lifecycle (create/update/disband), membership management (join/leave/kick/role), upload tokens, API keys, quota, and custom domains.- Zod DTOs in
packages/types/dto/nexium.tsfor all request/response validation (15+ schemas). - Barrel export via
packages/lib/nexium/index.ts.
- Nexium API Routes - 20+ REST endpoints for the complete Nexium platform.
- Profile:
GET/POST/PUT/DELETE /api/nexium/profile,GET /api/nexium/profile/[handle](public lookup). - Skills:
GET/POST /api/nexium/skills,PUT/DELETE /api/nexium/skills/[id]— supports add, bulk replace, and reorder via POST. - Signals:
GET/POST /api/nexium/signals,PUT/DELETE /api/nexium/signals/[id]— supports add and reorder via POST. - Opportunities:
GET/POST /api/nexium/opportunities,GET/PUT/DELETE /api/nexium/opportunities/[id],GET/POST/DELETE /api/nexium/opportunities/[id]/apply. - Squads:
GET/POST /api/nexium/squads(with?mine=truefilter),GET/PUT/DELETE /api/nexium/squads/[id],POST/DELETE /api/nexium/squads/[id]/members. - All routes use
requireAuth()with appropriate ownership checks; public endpoints available for discovery.
- Profile:
- Nexium Squad Infrastructure - Teams/squads with quotas, billing, API access, upload tokens, and plans.
NexiumSquadmodel withstorageUsed,storageQuotaMB,uploadToken(unique UUID), andstripeCustomerIdfor Stripe billing integration.NexiumSquadSubscriptionmodel mirrors userSubscription, reuses existingProducttable for plan management.NexiumSquadApiKeymodel withnsk_prefixed keys, SHA-256 hashed storage, prefix display (first 12 chars), andlastUsedAttracking. Max 10 keys per squad.- Upload token management:
GET/POST /api/nexium/squads/[id]/token— generate and rotate Bearer tokens for squad file uploads. - API key management:
GET/POST /api/nexium/squads/[id]/keys,DELETE /api/nexium/squads/[id]/keys/[keyId]— full key shown once on creation, only prefix visible after. - Quota endpoint:
GET /api/nexium/squads/[id]/quota— returns plan name, storage used/quota, upload size cap, percent usage. - Free tier defaults: 5 GB storage, 500 MB upload size cap, 3 custom domains, 10 API keys.
- Prisma migration:
20260330135926_add_nexium_squad_billing_and_api_keys.
- Nexium Squad Custom Domains - Squads can own and manage custom domains independently from users.
CustomDomainmodel updated:userIdmade nullable, newsquadIdforeign key andNexiumSquadrelation added.- Domain management API:
GET/POST /api/nexium/squads/[id]/domains,DELETE /api/nexium/squads/[id]/domains/[domainId]. - Domain limits enforced per plan (3 for free tier, unlimited for paid plans via subscription lookup).
- Cloudflare integration for domain registration and removal via existing
registerCustomDomain/removeCustomDomainutilities. - Upload validation updated:
validateSquadCustomDomain()validates domain ownership for squad-authenticated uploads.
- Nexium Squad Authentication - Squad-level Bearer token and API key authentication integrated into existing auth system.
- New
AuthenticatedSquadtype inapi-auth.tswithsquadId,slug,ownerUserId,storageUsed,storageQuotaMB, andauthMethod(upload_token|api_key). getSquadFromBearerToken(req): Authenticates squads via Bearer header — tries squad upload token first, then SHA-256 hashesnsk_prefixed keys againstNexiumSquadApiKey.keyHash.requireSquadAuth(req): Wrapper returning{ squad, response }for use in API route handlers.- User domain validation extended:
validateCustomDomain()now accepts domains owned by any squad the user is a member of.
- New
- Nexium Squad ShareX Config - Pre-configured ShareX
.sxcudownload for squad file uploads.- New endpoint:
GET /api/nexium/squads/[id]/sharex— generates and downloads a ShareX config file. - Config uses squad's upload token as Bearer auth, prefers squad's primary custom domain if available.
- Version 15.0.0 format with MultipartFormData body,
{json:data.url}response URL parsing. - Only accessible to squad members with an active upload token.
- New endpoint:
- Nexium Dashboard - Standalone squad management dashboard at
/dashboard/nexiumwith full glass-card styling.- Dashboard list page: View all squads the user belongs to, create new squads inline, status badges (Forming/Active/Completed/Disbanded), member counts.
- Squad detail page at
/dashboard/nexium/squads/[id]with membership verification and role-based access. - Tabbed squad management interface with 6 tabs:
- Overview: Squad info cards (members, domains, API keys), slug, max size, visibility, skills tags.
- Members: Member list with avatars, role badges (Owner/Member/Observer), kick functionality for owners.
- Uploads: Upload token display/hide/copy/rotate, ShareX
.sxcuconfig download button. - API Keys: Create named keys, one-time full key display with copy, revoke keys, prefix-only listing with last-used dates.
- Domains: Add/remove custom domains, verified/pending status badges, primary domain indicator.
- Storage: Plan name, storage usage progress bar with color thresholds (green/yellow/red), max upload size display.
- Lazy-loaded tab data fe...
[1.4.0] - Status Page & Quality of Life
Added
- Public User API Access - Added
/api/usersto public paths for contribution stats visibility.- Public profiles can now display GitHub contribution statistics without authentication.
- Ensures contribution data is accessible for public profile pages.
- Custom Status Page System - Comprehensive system status page powered by Instatus API integration.
- New
/statuspage displaying real-time service health, incidents, and maintenance windows. - TypeScript types for full Instatus API coverage (
packages/types/instatus.ts):StatusSummary,StatusComponent,Incident,Maintenance, and related interfaces. - Instatus client library (
packages/lib/instatus/index.ts) with public and authenticated API support. - Supports both public API (
/summary.json,/v2/components.json) and authenticated API (/v2/:page_id/...) with Bearer token. - Environment variables:
INSTATUS_API_KEY,INSTATUS_PAGE_ID,INSTATUS_STATUS_URLfor configuration. - API routes:
/api/status,/api/status/components,/api/status/incidents,/api/status/maintenances. - Status components:
StatusBadge,StatusIcon,StatusHeader,ComponentsList,ActiveIncidentsPanel,ActiveMaintenancesPanel,IncidentHistory,MaintenanceHistory,UptimeDisplay,StatusPageSkeleton. - Tabbed interface organizing Components, Incidents, and Maintenances with count badges.
- Glass-morphism styling consistent with rest of site using
GlassCardcomponents. - Expandable incident/maintenance cards showing update timelines with HTML message support.
- Parent-child component hierarchy built dynamically from flat API responses using
group.idreferences. - Auto-refresh capability with manual refresh button and last-updated timestamps.
- Responsive design with mobile-optimized tab navigation.
- New
- Media Kit Generator Script - Automated media kit generation for press and promotional use.
- New
scripts/generate-media-kit.tsscript generates complete media kit zip file. - Run with
bun run media-kitto generate/public/emberly-media-kit.zip. - Includes: logos (SVG, PNG), brand guidelines, color palette documentation, typography guide, promotional videos.
- Auto-generates README, BRAND_GUIDELINES.md, COLOR_PALETTE.md, and TYPOGRAPHY.md documentation.
- Copies all video assets from
/public/videos/with technical specs and usage guidelines. - Creates downloadable zip using PowerShell (Windows) or native zip (Unix).
- New
- Promotional Videos in Media Kit - Added video showcase section to press media kit page.
- New Promotional Videos section in
/press/media-kitpage between Logo Assets and Color Palette. - Client-side
VideoPlayercomponent with hydration-safe rendering to avoid SSR mismatches. - Videos display with native HTML5 controls, download buttons, and duration indicators.
- Videos:
site-preview-ad.mp4(interface overview),uploading-ad.mp4(upload flow demo).
- New Promotional Videos section in
- Community Documentation - Added open source contribution and conduct guidelines.
CONTRIBUTING.mdwith development setup, coding standards, commit conventions, and PR guidelines.CODE_OF_CONDUCT.mdbased on Contributor Covenant 2.1 with enforcement guidelines.- Contact information updated to use correct domain (
hey@embrly.ca, Discord invite link).
Changed
- Profile Dashboard Tab Navigation - Migrated from select menu to proper icon-based tabs.
- Replaced Select dropdown with horizontal TabsList component for better UX.
- Added icons to all 10 profile tabs: Profile (User), Billing (CreditCard), Uploads (Upload), Security (Shield), Perks (Gift), Referrals (Users), Notifications (Bell), Appearance (Palette), Testimonials (MessageSquare), Data (Database).
- Gradient glow effect behind tab container for visual depth.
- Responsive design: icons only on mobile (
hidden sm:inlinefor labels), full labels on larger screens. - Active tab styling with primary color highlight, subtle border, and shadow.
- Glassmorphism tab container with backdrop blur and semi-transparent background.
- Referrals Section Responsive Design - Improved mobile experience for referral code creation.
- Form input and "Create Code" button now stack vertically on mobile (
flex-col sm:flex-row). - Button stretches to full width on mobile for better tap targets.
- Code requirements text uses
break-wordsto prevent overflow on narrow screens. - "How Billing Credits Work" sections have responsive padding (
p-3 sm:p-4) and list margins (ml-3 sm:ml-4). - List items use
break-wordsclass for long text handling.
- Form input and "Create Code" button now stack vertically on mobile (
- Markdown Table Rendering - Added horizontal scrolling and improved table styling.
- Tables wrapped in scrollable container with
overflow-x-autofor mobile responsiveness. - Custom table header styling with subtle background color (
bg-white/5 dark:bg-black/10). - Table headers use
whitespace-nowrapto prevent awkward text breaking. - Consistent padding and border styling on all table cells.
- Hover effect on table rows for better interactivity (
hover:bg-white/5). - Negative margin with padding (
-mx-2 px-2) allows tables to use full width while maintaining scroll container.
- Tables wrapped in scrollable container with
- Environment Variable Consolidation - Unified domain configuration to use existing
NEXT_PUBLIC_BASE_URL.- Updated OAuth routes (GitHub and Discord) to use
NEXT_PUBLIC_BASE_URLinstead of deprecatedNEXT_PUBLIC_APP_URL. - Changed fallback domain from
https://emberly.sitetohttps://emberly.caacross all authentication flows. - Updated root layout metadata base URL to ensure proper Open Graph and Twitter card generation.
- Ensures consistent redirect URI configuration between OAuth initiation and callback handling.
- Updated OAuth routes (GitHub and Discord) to use
- GitHub Actions Build Workflow Simplified - Removed unnecessary PostgreSQL service from CI.
- Build workflow no longer spins up PostgreSQL container since
prisma generatedoesn't require a database connection. - Reduced CI complexity and build times by eliminating unused service dependency.
- Workflow now: checkout → setup Node/Bun → install dependencies → prisma generate → build.
- Build workflow no longer spins up PostgreSQL container since
- Security Section Responsive Design - Enhanced mobile experience for login history and session management.
- Login history header now stacks vertically on mobile (
flex-col sm:flex-row) with proper gap spacing. - Action buttons (Refresh, Sign Out Everywhere) now stretch to full width on mobile (
flex-1 sm:flex-none). - Current session card padding reduced on mobile (
p-3 sm:p-4) with responsive text sizing (text-xs sm:text-sm). - Login entry cards feature smaller gaps on mobile (
gap-2 sm:gap-3) and responsive padding (p-1.5 sm:p-2). - Device icons and metadata icons use
flex-shrink-0to prevent squishing on narrow screens. - Added
break-allfor IP addresses andbreak-wordsfor location text to prevent overflow.
- Login history header now stacks vertically on mobile (
- Recovery Codes UI Overhaul - Converted to modal-based display with improved accessibility.
- Removed Card wrapper in favor of cleaner inline section layout matching other security components.
- Recovery codes now display in a Dialog modal instead of inline expansion for better focus and security.
- Modal features scrollable code list with
max-h-[50vh]preventing viewport overflow on long lists. - Statistics grid improved with responsive layout: 2 columns on mobile, 4 columns on desktop (
grid-cols-2 sm:grid-cols-4). - Text sizes scale appropriately:
text-xs sm:text-smfor labels,text-xl sm:text-2xlfor stats. - Action buttons (View Codes, Regenerate Codes) stack vertically on mobile with full width.
- Modal action buttons (Download as File, Copy All Codes) use default size instead of small for better tap targets.
- Code display cards feature
break-allon code text to handle long strings gracefully. - Warning banners and important notices use responsive text sizing for readability.
- GitHub Contributions API Optimization - Improved reliability and performance of contribution statistics.
- Reduced commits fetched per repository from 10 to 5 to avoid GitHub API rate limiting.
- Added individual try-catch blocks around commit detail fetches to prevent single failures from breaking entire stats.
- Improved error handling with specific error logging per commit and repository.
- Stats calculation (additions, deletions, files changed) now properly accumulates even with partial failures.
- Press Pages Theme Compatibility - Fixed hardcoded colors to respect active theme.
- Press page hero section now uses theme variables (
text-foreground,text-primary) instead of hardcoded colors. - Media kit color palette dynamically pulls from CSS variables to display actual theme colors.
- Color swatches show live theme values with proper hex code extraction from computed styles.
- Press page hero section now uses theme variables (
Fixed
- Rich Embeds Metadata System - Fixed inconsistent behavior where
enableRichEmbedssetting was not respected for all file types.- Images now respect
enableRichEmbeds=false: Previously images always showed preview regardless of setting; now returns minimal metadata with no image preview. - Videos now respect
enableRichEmbeds=false: Previously videos still generated video metadata; now returns minimal metadata with no media. - Middleware now checks user settings: Updated
bot-handler.tsto query user'senableRichEmbedssetting from database before redirecting bot requests. - No embed metadata when disabled: When rich embeds are disabled,
buildMinimalMetadata()returns pure metadata with NOopenGraphortwittercard data, ensuring Discord/Twitter show plain links without any embed cards. - Videos work properly when enabled: When
enableRichEmbeds=true, videos use raw URL directly so Discord/Twitter can extract their own thumbnail and play the video inlin...
- Images now respect
[1.3.0] - Public Profiles and More.
Added
- Enhanced Public Profile System - Comprehensive user profile pages with GitHub integration and milestone-based perk displays.
- Tab-based interface with Overview, Contributions, and Files sections for organized content presentation.
- Milestone tier system display: Bronze (🥉), Silver (🥈), Gold (🥇), Platinum (💎), and Diamond (💠) badges for contributors and Discord boosters.
- Real-time contribution stats fetched from GitHub API showing lines of code, repository activity, and commit history.
- Public files showcase displaying user's shared files with view counts, download stats, and upload dates.
- Social account badges: Discord username badge with themed styling, GitHub profile link with external indicator.
- Perk benefits breakdown showing tier-specific storage bonuses and custom domain slot allocations.
- "How to Earn Perks" section guiding users on becoming contributors, Discord boosters, or affiliates.
- GitHub Contributions API (
/api/users/[id]/contributions) - Detailed contribution metrics and activity tracking.- Fetches total lines of code contributed across EmberlyOSS repositories.
- Lists contributed repositories with name, description, programming language, and star count.
- Retrieves up to 10 most recent commits with SHA, message, date, and repository name.
- Collects commit statistics: files changed, lines added (green), lines deleted (red).
- Aggregates total contribution stats: cumulative files changed, additions, deletions, and repository count.
- Uses GitHub PAT for public profile viewing to avoid rate limiting and token exposure.
- Public Files API (
/api/users/[id]/public-files) - Public file listing endpoint.- Queries user's public files (visibility: 'PUBLIC') with comprehensive metadata.
- Returns file details: name, URL path, MIME type, size, view count, download count, upload date.
- Limits results to 20 most recent files ordered by upload date descending.
- Generates full URLs for direct file access.
- Dashboard Profile Query Parameters - URL-based tab navigation support.
- Added
?tab=query parameter support to profile settings page (e.g.,/dashboard/profile?tab=security). - Tab state syncs with URL using
window.history.pushStatefor shareable links. - Initial tab selection reads from URL on page load with validation against available tabs.
- Works with both tabs component and select menu for consistent navigation experience.
- Added
Changed
- Public Profile Architecture Refactored - Switched from API-based to direct database access.
- Removed intermediate API route calls in favor of server-side Prisma queries for better performance.
- Updated to use Next.js 15 async params pattern (
await params) throughout dynamic routes. - Implemented GlassCard component pattern for visual consistency across profile sections.
- Consolidated perk calculation logic in server components to reduce client-side processing.
- Perk Display System Enhanced - Accurate milestone-based benefit visualization.
- Updated to display actual tier names (Bronze/Silver/Gold/Platinum/Diamond) instead of generic labels.
- Contribution statistics now show precise lines of code count with locale formatting.
- Discord booster duration displayed in months with proper pluralization.
- Storage and domain bonuses calculated from milestone constants with tier-specific values.
- Added visual tier icons for quick recognition of achievement levels.
- API Route Organization - Consolidated dynamic routes under consistent parameter naming.
- Moved contribution and file endpoints from
/api/users/[username]/to/api/users/[id]/to avoid route conflicts. - Renamed files endpoint to
public-filesto prevent collision with existing/api/users/[id]/files/[fileId]/route. - Updated routes to accept id, urlId, vanityId, or username for flexible user lookup.
- Frontend updated to use
user.idfor API calls ensuring consistent identifier usage.
- Moved contribution and file endpoints from
- GitHub Integration Display - Rich commit history and repository contribution visualization.
- Contributions tab now shows detailed commit cards with truncated SHA hashes and clickable links.
- Added color-coded statistics: green for additions, red for deletions, blue for files changed.
- Repository cards display with hover effects and external link indicators.
- Commit metadata includes repository name, file count, line changes, and formatted dates.
Fixed
- Next.js 15 Compatibility Issues - Resolved dynamic route parameter handling errors.
- Fixed "params is a Promise" errors by properly awaiting params in all dynamic route handlers.
- Removed invalid
fetchcache options causing build-time errors. - Updated API route type definitions to use
Promise<{ id: string }>parameter types.
- Prisma Schema Field Errors - Corrected field name mismatches across queries.
- Fixed
accountsfield references to use correctlinkedAccountsrelation name. - Updated
usernamefield access to useproviderUsernamefrom LinkedAccount model. - Corrected provider account data structure queries for GitHub and Discord integrations.
- Fixed
- Dynamic Route Naming Conflicts - Resolved slug parameter conflicts in API routes.
- Eliminated "You cannot use different slug names for the same dynamic path" error.
- Standardized on
[id]parameter naming throughout/api/users/route hierarchy. - Prevented route collision between user files endpoint and existing file management routes.
- Public Profile 404 Errors - Fixed routing and data fetching issues.
- Resolved profile not found errors by ensuring proper user lookup across urlId, vanityId, and name fields.
- Added
isProfilePublic: truefilter to all public profile queries for privacy enforcement. - Implemented proper null handling and not-found redirects for non-existent or private profiles.
[1.2.0] - User Referrals and More
Added
- 2FA Recovery Codes System - One-time backup codes for account recovery if authenticator is lost.
- Generate 10 recovery codes when enabling 2FA, displayed only once to the user.
- Codes stored in database with used/unused tracking and timestamps.
- Users can regenerate codes anytime to invalidate old ones and create new ones.
- Recovery codes work as valid 2FA authentication alongside TOTP codes.
- Enhanced Recovery Codes UI - Beautiful glass-morphism design with individual copy buttons, copy-all functionality, and download as text file.
- View Codes Anytime - "View Codes" button allows users to fetch and display all unused recovery codes on demand (not just after generation).
- Individual code copy buttons with visual feedback (checkmark animation on copy).
- Numbered code list with monospace font, hover effects, and responsive layout.
- Prominent download and copy-all action buttons side-by-side.
- Session storage persistence for recently generated codes (cleared on browser close).
- Visual status dashboard showing total codes, used codes, and remaining codes with count badges.
- Warning indicators when only 3 or fewer codes remain.
- Automatic invalidation of all codes when 2FA is disabled.
- New
TwoFactorRecoveryCodemodel with batchId for tracking code generations. /api/profile/2fa/recovery-codesendpoints for status, regeneration, and download with optional?includeCodes=trueparameter to fetch unused codes.RecoveryCodesManagercomponent in security settings to manage codes.getUnusedRecoveryCodes()utility function to retrieve all unused recovery codes for a user.
- Environment configuration template (
.env.template) with clear placeholder values, helpful comments, and format examples for all required environment variables. - Complete two-factor authentication (2FA) system using TOTP (Time-based One-Time Password) with authenticator app support.
- New
TwoFactorFormcomponent for secure 6-digit authenticator code entry with real-time validation. - 2FA enforcement in NextAuth credentials provider: users with
twoFactorEnabledmust enter a valid authenticator code before login succeeds. - Added
twoFactorEnabledandtwoFactorSecretfields to JWT and session interfaces for persistent 2FA state tracking. - Proper error-based 2FA flow: throws
TwoFactorRequirederror when 2FA is enabled but code not provided, forcing frontend to show 2FA form. - Support for both password and magic link authentication paths with identical 2FA enforcement.
- New
- Comprehensive metadata coverage across all pages:
- Added metadata exports to home page using
buildSiteMetadata()for dynamic OG/Twitter image generation. - Added metadata to all admin pages: User Management, Platform Settings, Blog Management, Products, Docs Management, Legal Pages, Partner Management, Testimonial Management, Audit Logs, and Email Broadcasts.
- All metadata uses consistent
buildPageMetadata()helper with title and description for SEO and social previews.
- Added metadata exports to home page using
- GitHub/Discord account linking system for social authentication and perk system integration.
- OAuth endpoints for GitHub (
/api/auth/link/github) and Discord (/api/auth/link/discord) account linking. - Automatic contributor detection: users with 1000+ lines of code across EmberlyOSS repos get +1GB storage per 1000 LOC.
- Automatic Discord booster detection: server boosters get +5GB storage + 1 custom domain slot.
- LinkedAccount model to store OAuth connections with access tokens and provider metadata.
- Perk utilities for calculating storage/domain bonuses and managing contributor levels.
- OAuth endpoints for GitHub (
- Cross-domain session sharing via Redis for seamless login across multiple domains.
- Switched from JWT to database session strategy using Redis adapter.
- Sessions now shared between
emberly.siteandembrly.cadomains automatically. - Users log in on one domain and are authenticated on both.
- Enhanced login history tracking with complete device and IP information.
- Captures client IP address with support for Vercel, Cloudflare, and standard x-forwarded-for headers.
- Stores user agent, device fingerprint, country, and city for each login.
- Displays parsed device type (Desktop/Mobile/Tablet), browser, and OS in security dashboard.
- Device icons and detailed geographic information shown in login history.
- New device detection triggers automatic email alerts for suspicious logins.
- Billing Credits & Transaction Logging System - Comprehensive credit tracking for audit trail and financial transparency.
- New
CreditTransactionPrisma model with userId, type, amount, description, and related metadata fields. - Transaction logging for all credit operations: referral earnings, credit applications, manual adjustments, and refunds.
/api/profile/billing-historyendpoint for retrieving credit transaction history with graceful error handling.BillingCreditsSectioncomponent in user profile displaying current balance, Stripe customer balance, and recent transaction activity.- Transaction metadata includes relatedUserId for referral tracking and relatedOrderId for purchase attribution.
- Automatic transaction logging when credits are applied to Stripe customer balance during checkout.
- New
- Custom Referral Codes System - User-friendly referral code creation replacing auto-generated codes.
- Users can create custom, memorable referral codes (3-30 characters, alphanumeric with hyphens/underscores).
- Custom code form in profile with real-time validation feedback.
- Validation prevents reserved words (admin, api, auth, dashboard, settings, profile, billing, null).
- Brand name protection blocks referral codes containing trademarked terms (emberly, pixelated, codemeapixel).
- Case-insensitive substring matching ensures brand variations cannot bypass restrictions.
/api/profile/referralsPOST endpoint supports creating custom codes with comprehensive validation.- Two-state referral component: creation form when no code exists, full statistics and sharing options when code is active.
- Enhanced Payment Routes with Centralized Stripe Utilities - Consolidated payment processing infrastructure.
- All payment routes (
/api/payments/portal,/api/payments/webhook,/api/payments/checkout,/api/payments/purchase) refactored to use centralized utilities. ensureStripeCustomer()utility validates and creates Stripe customer IDs consistently across all payment flows.applyReferralCreditsToStripe()utility manages credit application and transaction logging with metadata tracking.- Webhook handler enhanced with credit transaction logging for purchase completion events.
- Checkout and purchase routes now include order metadata for credit transaction attribution.
- All payment routes (
Changed
- Metadata system significantly refactored and simplified:
- Consolidated metadata building from ~400+ lines across multiple helper functions into streamlined
buildRichMetadata(),buildSiteMetadata(), andbuildPageMetadata()functions. - Removed 150+ lines of unnecessary helper functions (
buildOpenGraphImages,buildOpenGraphAudio,buildTwitterMetadata,buildOtherMetadata). - Removed hardcoded image references in favor of Next.js auto-detection of
opengraph-image.tsxandtwitter-image.tsx. - Improved URL handling using URL constructor instead of manual string manipulation.
enableRichEmbedssetting now properly enforced: disables rich metadata for both images and videos, returning minimal metadata instead.- Password-protected files now return minimal metadata to prevent embedding sensitive content in social previews.
- Added strict
fileIdvalidation requirement for rich metadata generation to ensure thumbnail URLs are available.
- Consolidated metadata building from ~400+ lines across multiple helper functions into streamlined
- Login form updated to detect 2FA requirement and display
TwoFactorFormcomponent:- Stores pending credentials during 2FA verification flow.
- Clear error messaging for invalid authenticator codes.
- "Back to login" button allows users to restart authentication flow.
- Dashboard metadata conflicts resolved:
- Removed redundant metadata definitions from
dashboard/layout.tsxandpricing/layout.tsx. - Each dashboard page now defines its own specific metadata with unique titles and descriptions.
- Applied
buildPageMetadata()consistently across all dashboard pages.
- Removed redundant metadata definitions from
- Short URL layout metadata cleaned up:
- Removed 30+ lines of verbose metadata with explicit null/empty field definitions.
- Simplified to only include essential
robots: { index: false, follow: false }to prevent search engine indexing.
- OG/Twitter image generation updated to use actual Emberly logo instead of placeholder.
- Logo now renders with dynamic colors based on selected theme.
- Proper two-color flame icon design applied to social media previews.
- Storage quota system now includes perk bonuses.
- Domain slot calculation includes Discord booster +1 domain bonus.
- Storage quota calculation includes contributor and booster storage bonuses.
- Perk bonuses stack additively for users with multiple perk roles.
- README updated to clearly establish this as the Emberly Cloud instance:
- Added tech stack documentation (Next.js 14, TypeScript, PostgreSQL, Prisma, NextAuth.js, Tailwind CSS, shadcn/ui).
- Clarified distinction between this cloud-hosted repository and the upcoming self-hosted open-source distribution.
- Included cloud-specific features (Stripe billing, Resend email, analytics, 2FA).
- Added development setup instructions.
Fixed
- Critical 2FA enforcement vulnerability: users with 2FA enabled were able to bypass authentication without entering an authenticator code.
- Root cause: NextAuth's
authorizefunction was returning user objects on password validation, which NextAuth int...
- Root cause: NextAuth's