1.12.2 release -> main (#2181)
Merge 1.12.2 release changes into main, includes: - Collection replay full refresh on metadata / archived items (#2176) - Fix for self-registration default org (#2178) - Prepend missing https in start URL (#2177) - Updated billing to support free trial messaging (#2179) --------- Co-authored-by: sua yoo <sua@webrecorder.org> Co-authored-by: Henry Wilkinson <henry@wilkinson.graphics> Co-authored-by: sua yoo <sua@suayoo.com> Co-authored-by: SuaYoo <SuaYoo@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									37c0b06622
								
							
						
					
					
						commit
						50dac7dc50
					
				| @ -115,6 +115,7 @@ class OrgOps: | |||||||
| 
 | 
 | ||||||
|     invites: InviteOps |     invites: InviteOps | ||||||
|     user_manager: UserManager |     user_manager: UserManager | ||||||
|  |     register_to_org_id: Optional[str] | ||||||
|     base_crawl_ops: BaseCrawlOps |     base_crawl_ops: BaseCrawlOps | ||||||
|     default_primary: Optional[StorageRef] |     default_primary: Optional[StorageRef] | ||||||
| 
 | 
 | ||||||
| @ -295,7 +296,7 @@ class OrgOps: | |||||||
|         """Get default organiation for new user registration, or default org""" |         """Get default organiation for new user registration, or default org""" | ||||||
|         if self.register_to_org_id: |         if self.register_to_org_id: | ||||||
|             try: |             try: | ||||||
|                 await self.get_org_by_id(UUID(self.register_to_org_id)) |                 return await self.get_org_by_id(UUID(self.register_to_org_id)) | ||||||
|             except HTTPException as exc: |             except HTTPException as exc: | ||||||
|                 raise HTTPException( |                 raise HTTPException( | ||||||
|                     status_code=500, detail="default_register_org_not_found" |                     status_code=500, detail="default_register_org_not_found" | ||||||
|  | |||||||
| @ -143,7 +143,7 @@ class StorageOps: | |||||||
|             use_access_for_presign = False |             use_access_for_presign = False | ||||||
|         else: |         else: | ||||||
|             access_endpoint_url = storage.get("access_endpoint_url") or endpoint_url |             access_endpoint_url = storage.get("access_endpoint_url") or endpoint_url | ||||||
|             use_access_for_presign = True |             use_access_for_presign = is_bool(storage.get("use_access_for_presign")) | ||||||
| 
 | 
 | ||||||
|         return S3Storage( |         return S3Storage( | ||||||
|             access_key=storage["access_key"], |             access_key=storage["access_key"], | ||||||
|  | |||||||
| @ -75,7 +75,7 @@ allow_dupe_invites: "0" | |||||||
| invite_expire_seconds: 604800 | invite_expire_seconds: 604800 | ||||||
| 
 | 
 | ||||||
| # base url for replayweb.page | # base url for replayweb.page | ||||||
| rwp_base_url: "https://cdn.jsdelivr.net/npm/replaywebpage@2.1.4/" | rwp_base_url: "https://cdn.jsdelivr.net/npm/replaywebpage@2.2.4/" | ||||||
| 
 | 
 | ||||||
| superuser: | superuser: | ||||||
|   # set this to enable a superuser admin |   # set this to enable a superuser admin | ||||||
|  | |||||||
| @ -555,7 +555,7 @@ export class CollectionItemsDialog extends BtrixElement { | |||||||
|     let selectionMessage = msg("No changes to save"); |     let selectionMessage = msg("No changes to save"); | ||||||
| 
 | 
 | ||||||
|     if (hasChange) { |     if (hasChange) { | ||||||
|       const messages = []; |       const messages: string[] = []; | ||||||
|       if (addCount) { |       if (addCount) { | ||||||
|         messages.push( |         messages.push( | ||||||
|           msg( |           msg( | ||||||
| @ -565,7 +565,9 @@ export class CollectionItemsDialog extends BtrixElement { | |||||||
|       } |       } | ||||||
|       if (removeCount) { |       if (removeCount) { | ||||||
|         messages.push( |         messages.push( | ||||||
|           str`Adding ${this.localize.number(removeCount)} ${pluralOf("items", removeCount)}`, |           msg( | ||||||
|  |             str`Removing ${this.localize.number(removeCount)} ${pluralOf("items", removeCount)}`, | ||||||
|  |           ), | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -112,7 +112,6 @@ const DEFAULT_BEHAVIORS = [ | |||||||
|   "autofetch", |   "autofetch", | ||||||
|   "siteSpecific", |   "siteSpecific", | ||||||
| ]; | ]; | ||||||
| const MAX_ADDITIONAL_URLS = 100; |  | ||||||
| 
 | 
 | ||||||
| const getDefaultProgressState = (hasConfigId = false): ProgressState => { | const getDefaultProgressState = (hasConfigId = false): ProgressState => { | ||||||
|   let activeTab: StepName = "crawlSetup"; |   let activeTab: StepName = "crawlSetup"; | ||||||
| @ -163,7 +162,8 @@ function getLocalizedWeekDays() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function validURL(url: string) { | function validURL(url: string) { | ||||||
|   return /((((https?):(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w\-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\\w]*))?)/.test( |   // adapted from: https://gist.github.com/dperini/729294
 | ||||||
|  |   return /^(?:https?:\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( | ||||||
|     url, |     url, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
| @ -174,7 +174,8 @@ const urlListToArray = flow( | |||||||
|   trimArray, |   trimArray, | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| const URL_LIST_MAX_URLS = 1000; | //todo: make this customizable, perhaps at deploy time
 | ||||||
|  | const URL_LIST_MAX_URLS = 100; | ||||||
| 
 | 
 | ||||||
| type CrawlConfigResponse = { | type CrawlConfigResponse = { | ||||||
|   run_now_job?: boolean; |   run_now_job?: boolean; | ||||||
| @ -814,6 +815,17 @@ export class WorkflowEditor extends BtrixElement { | |||||||
|                     const text = msg("Please enter a valid URL."); |                     const text = msg("Please enter a valid URL."); | ||||||
|                     inputEl.helpText = text; |                     inputEl.helpText = text; | ||||||
|                     inputEl.setCustomValidity(text); |                     inputEl.setCustomValidity(text); | ||||||
|  |                   } else if ( | ||||||
|  |                     inputEl.value && | ||||||
|  |                     !inputEl.value.startsWith("https://") && | ||||||
|  |                     !inputEl.value.startsWith("http://") | ||||||
|  |                   ) { | ||||||
|  |                     this.updateFormState( | ||||||
|  |                       { | ||||||
|  |                         urlList: "https://" + inputEl.value, | ||||||
|  |                       }, | ||||||
|  |                       true, | ||||||
|  |                     ); | ||||||
|                   } |                   } | ||||||
|                 }} |                 }} | ||||||
|               > |               > | ||||||
| @ -835,19 +847,8 @@ https://archiveweb.page/guide`} | |||||||
|                 required |                 required | ||||||
|                 @keyup=${async (e: KeyboardEvent) => { |                 @keyup=${async (e: KeyboardEvent) => { | ||||||
|                   if (e.key === "Enter") { |                   if (e.key === "Enter") { | ||||||
|                     const inputEl = e.target as SlInput; |                     await (e.target as SlInput).updateComplete; | ||||||
|                     await inputEl.updateComplete; |                     this.doValidateTextArea(e.target); | ||||||
|                     if (!inputEl.value) return; |  | ||||||
|                     const { isValid, helpText } = this.validateUrlList( |  | ||||||
|                       inputEl.value, |  | ||||||
|                       MAX_ADDITIONAL_URLS, |  | ||||||
|                     ); |  | ||||||
|                     inputEl.helpText = helpText; |  | ||||||
|                     if (isValid) { |  | ||||||
|                       inputEl.setCustomValidity(""); |  | ||||||
|                     } else { |  | ||||||
|                       inputEl.setCustomValidity(helpText); |  | ||||||
|                     } |  | ||||||
|                   } |                   } | ||||||
|                 }} |                 }} | ||||||
|                 @sl-input=${(e: CustomEvent) => { |                 @sl-input=${(e: CustomEvent) => { | ||||||
| @ -857,24 +858,16 @@ https://archiveweb.page/guide`} | |||||||
|                   } |                   } | ||||||
|                 }} |                 }} | ||||||
|                 @sl-change=${async (e: CustomEvent) => { |                 @sl-change=${async (e: CustomEvent) => { | ||||||
|                   const inputEl = e.target as SlInput; |                   this.doValidateTextArea(e.target); | ||||||
|                   if (!inputEl.value) return; |                 }} | ||||||
|                   const { isValid, helpText } = this.validateUrlList( |                 @sl-blur=${async (e: CustomEvent) => { | ||||||
|                     inputEl.value, |                   this.doValidateTextArea(e.target); | ||||||
|                     MAX_ADDITIONAL_URLS, |  | ||||||
|                   ); |  | ||||||
|                   inputEl.helpText = helpText; |  | ||||||
|                   if (isValid) { |  | ||||||
|                     inputEl.setCustomValidity(""); |  | ||||||
|                   } else { |  | ||||||
|                     inputEl.setCustomValidity(helpText); |  | ||||||
|                   } |  | ||||||
|                 }} |                 }} | ||||||
|               ></sl-textarea> |               ></sl-textarea> | ||||||
|             `)}
 |             `)}
 | ||||||
|             ${this.renderHelpTextCol( |             ${this.renderHelpTextCol( | ||||||
|               msg( |               msg( | ||||||
|                 str`The crawler will visit and record each URL listed here. You can enter up to ${this.localize.number(MAX_ADDITIONAL_URLS)} URLs.`, |                 str`The crawler will visit and record each URL listed here. You can enter up to ${this.localize.number(URL_LIST_MAX_URLS)} URLs.`, | ||||||
|               ), |               ), | ||||||
|             )} |             )} | ||||||
|           `}
 |           `}
 | ||||||
| @ -997,6 +990,17 @@ https://archiveweb.page/guide`} | |||||||
|               const text = msg("Please enter a valid URL."); |               const text = msg("Please enter a valid URL."); | ||||||
|               inputEl.helpText = text; |               inputEl.helpText = text; | ||||||
|               inputEl.setCustomValidity(text); |               inputEl.setCustomValidity(text); | ||||||
|  |             } else if ( | ||||||
|  |               inputEl.value && | ||||||
|  |               !inputEl.value.startsWith("https://") && | ||||||
|  |               !inputEl.value.startsWith("http://") | ||||||
|  |             ) { | ||||||
|  |               this.updateFormState( | ||||||
|  |                 { | ||||||
|  |                   primarySeedUrl: "https://" + inputEl.value, | ||||||
|  |                 }, | ||||||
|  |                 true, | ||||||
|  |               ); | ||||||
|             } |             } | ||||||
|           }} |           }} | ||||||
|         > |         > | ||||||
| @ -1099,19 +1103,8 @@ https://example.net`} | |||||||
| https://archiveweb.page/images/${"logo.svg"}`}
 | https://archiveweb.page/images/${"logo.svg"}`}
 | ||||||
|                 @keyup=${async (e: KeyboardEvent) => { |                 @keyup=${async (e: KeyboardEvent) => { | ||||||
|                   if (e.key === "Enter") { |                   if (e.key === "Enter") { | ||||||
|                     const inputEl = e.target as SlInput; |                     await (e.target as SlInput).updateComplete; | ||||||
|                     await inputEl.updateComplete; |                     this.doValidateTextArea(e.target); | ||||||
|                     if (!inputEl.value) return; |  | ||||||
|                     const { isValid, helpText } = this.validateUrlList( |  | ||||||
|                       inputEl.value, |  | ||||||
|                       MAX_ADDITIONAL_URLS, |  | ||||||
|                     ); |  | ||||||
|                     inputEl.helpText = helpText; |  | ||||||
|                     if (isValid) { |  | ||||||
|                       inputEl.setCustomValidity(""); |  | ||||||
|                     } else { |  | ||||||
|                       inputEl.setCustomValidity(helpText); |  | ||||||
|                     } |  | ||||||
|                   } |                   } | ||||||
|                 }} |                 }} | ||||||
|                 @sl-input=${(e: CustomEvent) => { |                 @sl-input=${(e: CustomEvent) => { | ||||||
| @ -1121,24 +1114,16 @@ https://archiveweb.page/images/${"logo.svg"}`} | |||||||
|                   } |                   } | ||||||
|                 }} |                 }} | ||||||
|                 @sl-change=${async (e: CustomEvent) => { |                 @sl-change=${async (e: CustomEvent) => { | ||||||
|                   const inputEl = e.target as SlInput; |                   this.doValidateTextArea(e.target); | ||||||
|                   if (!inputEl.value) return; |                 }} | ||||||
|                   const { isValid, helpText } = this.validateUrlList( |                 @sl-blur=${async (e: CustomEvent) => { | ||||||
|                     inputEl.value, |                   this.doValidateTextArea(e.target); | ||||||
|                     MAX_ADDITIONAL_URLS, |  | ||||||
|                   ); |  | ||||||
|                   inputEl.helpText = helpText; |  | ||||||
|                   if (isValid) { |  | ||||||
|                     inputEl.setCustomValidity(""); |  | ||||||
|                   } else { |  | ||||||
|                     inputEl.setCustomValidity(helpText); |  | ||||||
|                   } |  | ||||||
|                 }} |                 }} | ||||||
|               ></sl-textarea> |               ></sl-textarea> | ||||||
|             `)}
 |             `)}
 | ||||||
|             ${this.renderHelpTextCol( |             ${this.renderHelpTextCol( | ||||||
|               msg( |               msg( | ||||||
|                 str`The crawler will visit and record each URL listed here. You can enter up to ${this.localize.number(MAX_ADDITIONAL_URLS)} URLs.`, |                 str`The crawler will visit and record each URL listed here. You can enter up to ${this.localize.number(URL_LIST_MAX_URLS)} URLs.`, | ||||||
|               ), |               ), | ||||||
|             )} |             )} | ||||||
|           </div> |           </div> | ||||||
| @ -1147,6 +1132,21 @@ https://archiveweb.page/images/${"logo.svg"}`} | |||||||
|     `;
 |     `;
 | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   private doValidateTextArea(target: EventTarget | null) { | ||||||
|  |     const inputEl = target as SlInput; | ||||||
|  |     if (!inputEl.value) return; | ||||||
|  |     const { isValid, helpText } = this.validateUrlList( | ||||||
|  |       inputEl.value, | ||||||
|  |       URL_LIST_MAX_URLS, | ||||||
|  |     ); | ||||||
|  |     inputEl.helpText = helpText; | ||||||
|  |     if (isValid) { | ||||||
|  |       inputEl.setCustomValidity(""); | ||||||
|  |     } else { | ||||||
|  |       inputEl.setCustomValidity(helpText); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   private renderCrawlLimits() { |   private renderCrawlLimits() { | ||||||
|     // Max Pages minimum value cannot be lower than seed count
 |     // Max Pages minimum value cannot be lower than seed count
 | ||||||
|     const minPages = Math.max( |     const minPages = Math.max( | ||||||
| @ -2076,6 +2076,20 @@ https://archiveweb.page/images/${"logo.svg"}`} | |||||||
|           str`Please remove or fix the following invalid URL: ${invalidUrl}`, |           str`Please remove or fix the following invalid URL: ${invalidUrl}`, | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|  |       if (isValid) { | ||||||
|  |         // auto-add https:// prefix if otherwise a valid URL
 | ||||||
|  |         let updated = false; | ||||||
|  |         for (let i = 0; i < urlList.length; i++) { | ||||||
|  |           const url = urlList[i]; | ||||||
|  |           if (!url.startsWith("http://") && !url.startsWith("https://")) { | ||||||
|  |             urlList[i] = "https://" + url; | ||||||
|  |             updated = true; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         if (updated) { | ||||||
|  |           this.updateFormState({ urlList: urlList.join("\n") }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|     return { isValid, helpText }; |     return { isValid, helpText }; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import { html, type TemplateResult } from "lit"; | |||||||
| import { customElement } from "lit/decorators.js"; | import { customElement } from "lit/decorators.js"; | ||||||
| 
 | 
 | ||||||
| import { BtrixElement } from "@/classes/BtrixElement"; | import { BtrixElement } from "@/classes/BtrixElement"; | ||||||
|  | import { SubscriptionStatus } from "@/types/billing"; | ||||||
| import { OrgReadOnlyReason } from "@/types/org"; | import { OrgReadOnlyReason } from "@/types/org"; | ||||||
| 
 | 
 | ||||||
| type Alert = { | type Alert = { | ||||||
| @ -61,16 +62,32 @@ export class OrgStatusBanner extends BtrixElement { | |||||||
|       execMinutesQuotaReached, |       execMinutesQuotaReached, | ||||||
|     } = this.org; |     } = this.org; | ||||||
| 
 | 
 | ||||||
|  |     let daysDiff = 0; | ||||||
|  |     let dateStr = ""; | ||||||
|  |     const futureCancelDate = subscription?.futureCancelDate || null; | ||||||
|  | 
 | ||||||
|  |     if (futureCancelDate) { | ||||||
|  |       daysDiff = differenceInDays(new Date(), new Date(futureCancelDate)); | ||||||
|  | 
 | ||||||
|  |       dateStr = this.localize.date(futureCancelDate, { | ||||||
|  |         month: "long", | ||||||
|  |         day: "numeric", | ||||||
|  |         year: "numeric", | ||||||
|  |         hour: "numeric", | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const isTrial = subscription?.status === SubscriptionStatus.Trialing; | ||||||
|  | 
 | ||||||
|  |     // show banner if < this many days of trial is left
 | ||||||
|  |     const MAX_TRIAL_DAYS_SHOW_BANNER = 4; | ||||||
|  | 
 | ||||||
|     return [ |     return [ | ||||||
|       { |       { | ||||||
|         test: () => |         test: () => | ||||||
|           !readOnly && !readOnlyOnCancel && !!subscription?.futureCancelDate, |           !readOnly && !readOnlyOnCancel && !!futureCancelDate && !isTrial, | ||||||
| 
 | 
 | ||||||
|         content: () => { |         content: () => { | ||||||
|           const daysDiff = differenceInDays( |  | ||||||
|             new Date(), |  | ||||||
|             new Date(subscription!.futureCancelDate!), |  | ||||||
|           ); |  | ||||||
|           return { |           return { | ||||||
|             title: |             title: | ||||||
|               daysDiff > 1 |               daysDiff > 1 | ||||||
| @ -82,15 +99,7 @@ export class OrgStatusBanner extends BtrixElement { | |||||||
|             detail: html` |             detail: html` | ||||||
|               <p> |               <p> | ||||||
|                 ${msg( |                 ${msg( | ||||||
|                   str`Your subscription ends on ${this.localize.date( |                   str`Your subscription ends on ${dateStr}. Your user account, org, and all associated data will be deleted.`, | ||||||
|                     subscription!.futureCancelDate!, |  | ||||||
|                     { |  | ||||||
|                       month: "long", |  | ||||||
|                       day: "numeric", |  | ||||||
|                       year: "numeric", |  | ||||||
|                       hour: "numeric", |  | ||||||
|                     }, |  | ||||||
|                   )}. Your user account, org, and all associated data will be deleted.`,
 |  | ||||||
|                 )} |                 )} | ||||||
|               </p> |               </p> | ||||||
|               <p> |               <p> | ||||||
| @ -106,13 +115,43 @@ export class OrgStatusBanner extends BtrixElement { | |||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         test: () => |         test: () => | ||||||
|           !readOnly && readOnlyOnCancel && !!subscription?.futureCancelDate, |           !readOnly && | ||||||
|  |           !readOnlyOnCancel && | ||||||
|  |           !!futureCancelDate && | ||||||
|  |           isTrial && | ||||||
|  |           daysDiff < MAX_TRIAL_DAYS_SHOW_BANNER, | ||||||
|  | 
 | ||||||
|  |         content: () => { | ||||||
|  |           return { | ||||||
|  |             title: | ||||||
|  |               daysDiff > 1 | ||||||
|  |                 ? msg( | ||||||
|  |                     str`You have ${daysDiff} days left of your Browsertrix trial`, | ||||||
|  |                   ) | ||||||
|  |                 : msg(`Your trial ends within one day`), | ||||||
|  | 
 | ||||||
|  |             detail: html` | ||||||
|  |               <p> | ||||||
|  |                 ${msg( | ||||||
|  |                   html`Your free trial ends on ${dateStr}. To continue using
 | ||||||
|  |                     Browsertrix, select <strong>Choose Plan</strong> in | ||||||
|  |                     ${billingTabLink}.`,
 | ||||||
|  |                 )} | ||||||
|  |               </p> | ||||||
|  |               <p> | ||||||
|  |                 ${msg( | ||||||
|  |                   str`Your web archives are always yours — you can download any archived items you'd like to keep
 | ||||||
|  |                   before the trial ends!`,
 | ||||||
|  |                 )} | ||||||
|  |               </p> | ||||||
|  |             `,
 | ||||||
|  |           }; | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         test: () => !readOnly && readOnlyOnCancel && !!futureCancelDate, | ||||||
| 
 | 
 | ||||||
|         content: () => { |         content: () => { | ||||||
|           const daysDiff = differenceInDays( |  | ||||||
|             new Date(), |  | ||||||
|             new Date(subscription!.futureCancelDate!), |  | ||||||
|           ); |  | ||||||
|           return { |           return { | ||||||
|             title: |             title: | ||||||
|               daysDiff > 1 |               daysDiff > 1 | ||||||
| @ -121,20 +160,12 @@ export class OrgStatusBanner extends BtrixElement { | |||||||
|             detail: html` |             detail: html` | ||||||
|               <p> |               <p> | ||||||
|                 ${msg( |                 ${msg( | ||||||
|                   str`Your subscription ends on ${this.localize.date( |                   str`Your subscription ends on ${dateStr}. You will no longer be able to run crawls, upload files, create browser profiles, or create collections.`, | ||||||
|                     subscription!.futureCancelDate!, |  | ||||||
|                     { |  | ||||||
|                       month: "long", |  | ||||||
|                       day: "numeric", |  | ||||||
|                       year: "numeric", |  | ||||||
|                       hour: "numeric", |  | ||||||
|                     }, |  | ||||||
|                   )}. You will no longer be able to run crawls, upload files, create browser profiles, or create collections.`,
 |  | ||||||
|                 )} |                 )} | ||||||
|               </p> |               </p> | ||||||
|               <p> |               <p> | ||||||
|                 ${msg( |                 ${msg( | ||||||
|                   html`To keep your plan and continue crawling, see
 |                   html`To choose a plan and continue using Browsertrix, see
 | ||||||
|                   ${billingTabLink}.`,
 |                   ${billingTabLink}.`,
 | ||||||
|                 )} |                 )} | ||||||
|               </p> |               </p> | ||||||
|  | |||||||
| @ -60,6 +60,9 @@ export class CollectionDetail extends BtrixElement { | |||||||
|   @query(".descriptionExpandBtn") |   @query(".descriptionExpandBtn") | ||||||
|   private readonly descriptionExpandBtn?: HTMLElement | null; |   private readonly descriptionExpandBtn?: HTMLElement | null; | ||||||
| 
 | 
 | ||||||
|  |   @query("replay-web-page") | ||||||
|  |   private readonly replayEmbed?: ReplayWebPage | null; | ||||||
|  | 
 | ||||||
|   // Use to cancel requests
 |   // Use to cancel requests
 | ||||||
|   private getArchivedItemsController: AbortController | null = null; |   private getArchivedItemsController: AbortController | null = null; | ||||||
| 
 | 
 | ||||||
| @ -203,6 +206,7 @@ export class CollectionDetail extends BtrixElement { | |||||||
|         ?open=${this.openDialogName === "editItems"} |         ?open=${this.openDialogName === "editItems"} | ||||||
|         @sl-hide=${() => (this.openDialogName = undefined)} |         @sl-hide=${() => (this.openDialogName = undefined)} | ||||||
|         @btrix-collection-saved=${() => { |         @btrix-collection-saved=${() => { | ||||||
|  |           this.refreshReplay(); | ||||||
|           void this.fetchCollection(); |           void this.fetchCollection(); | ||||||
|           void this.fetchArchivedItems(); |           void this.fetchArchivedItems(); | ||||||
|         }} |         }} | ||||||
| @ -215,7 +219,10 @@ export class CollectionDetail extends BtrixElement { | |||||||
|             .collection=${this.collection!} |             .collection=${this.collection!} | ||||||
|             ?open=${this.openDialogName === "editMetadata"} |             ?open=${this.openDialogName === "editMetadata"} | ||||||
|             @sl-hide=${() => (this.openDialogName = undefined)} |             @sl-hide=${() => (this.openDialogName = undefined)} | ||||||
|             @btrix-collection-saved=${() => void this.fetchCollection()} |             @btrix-collection-saved=${() => { | ||||||
|  |               this.refreshReplay(); | ||||||
|  |               void this.fetchCollection(); | ||||||
|  |             }} | ||||||
|           > |           > | ||||||
|           </btrix-collection-metadata-dialog> |           </btrix-collection-metadata-dialog> | ||||||
|         `,
 |         `,
 | ||||||
| @ -223,6 +230,16 @@ export class CollectionDetail extends BtrixElement { | |||||||
|       ${this.renderShareDialog()}`;
 |       ${this.renderShareDialog()}`;
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   private refreshReplay() { | ||||||
|  |     if (this.replayEmbed) { | ||||||
|  |       try { | ||||||
|  |         this.replayEmbed.fullReload(); | ||||||
|  |       } catch (e) { | ||||||
|  |         console.warn("Full reload not available in RWP"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   private getPublicReplayURL() { |   private getPublicReplayURL() { | ||||||
|     return new URL( |     return new URL( | ||||||
|       `/api/orgs/${this.orgId}/collections/${this.collectionId}/public/replay.json`, |       `/api/orgs/${this.orgId}/collections/${this.collectionId}/public/replay.json`, | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ import { tw } from "@/utils/tailwind"; | |||||||
| const linkClassList = tw`transition-color text-primary hover:text-primary-500`; | const linkClassList = tw`transition-color text-primary hover:text-primary-500`; | ||||||
| const manageLinkClasslist = clsx( | const manageLinkClasslist = clsx( | ||||||
|   linkClassList, |   linkClassList, | ||||||
|   tw`flex items-center gap-2 p-2 text-sm font-semibold leading-none`, |   tw`flex cursor-pointer items-center gap-2 p-2 text-sm font-semibold leading-none`, | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| @localized() | @localized() | ||||||
| @ -41,6 +41,10 @@ export class OrgSettingsBilling extends BtrixElement { | |||||||
|     let label = msg("Manage Billing"); |     let label = msg("Manage Billing"); | ||||||
| 
 | 
 | ||||||
|     switch (subscription.status) { |     switch (subscription.status) { | ||||||
|  |       case SubscriptionStatus.Trialing: { | ||||||
|  |         label = msg("Choose Plan"); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|       case SubscriptionStatus.PausedPaymentFailed: { |       case SubscriptionStatus.PausedPaymentFailed: { | ||||||
|         label = msg("Update Billing"); |         label = msg("Update Billing"); | ||||||
|         break; |         break; | ||||||
| @ -112,32 +116,41 @@ export class OrgSettingsBilling extends BtrixElement { | |||||||
|                 </div> |                 </div> | ||||||
|                 ${when( |                 ${when( | ||||||
|                   this.org, |                   this.org, | ||||||
|                   (org) => |                   (org) => { | ||||||
|                     org.subscription?.futureCancelDate |                     if (!org.subscription?.futureCancelDate) { | ||||||
|                       ? html` |                       return nothing; | ||||||
|                           <div |                     } | ||||||
|                             class="mb-3 flex items-center gap-2 border-b pb-3 text-neutral-500" | 
 | ||||||
|                           > |                     const futureCancelDate = html`<sl-format-date
 | ||||||
|                             <sl-icon |                       class="truncate" | ||||||
|                               name="info-circle" |                       date=${org.subscription.futureCancelDate} | ||||||
|                               class="text-base" |                       month="long" | ||||||
|                             ></sl-icon> |                       day="numeric" | ||||||
|                             <span> |                       year="numeric" | ||||||
|                               ${msg( |                     > | ||||||
|  |                     </sl-format-date>`; | ||||||
|  | 
 | ||||||
|  |                     return html` | ||||||
|  |                       <div | ||||||
|  |                         class="mb-3 flex items-center gap-2 border-b pb-3 text-neutral-500" | ||||||
|  |                       > | ||||||
|  |                         <sl-icon name="info-circle" class="text-base"></sl-icon> | ||||||
|  |                         <span> | ||||||
|  |                           ${org.subscription.status === | ||||||
|  |                           SubscriptionStatus.Trialing | ||||||
|  |                             ? msg( | ||||||
|  |                                 html`Your trial will end on ${futureCancelDate} | ||||||
|  |                                   - Click <strong>Choose Plan</strong> to | ||||||
|  |                                   subscribe`,
 | ||||||
|  |                               ) | ||||||
|  |                             : msg( | ||||||
|                                 html`Your plan will be canceled on
 |                                 html`Your plan will be canceled on
 | ||||||
|                                   <sl-format-date |                                 ${futureCancelDate}`,
 | ||||||
|                                     class="truncate" |  | ||||||
|                                     date=${org.subscription.futureCancelDate} |  | ||||||
|                                     month="long" |  | ||||||
|                                     day="numeric" |  | ||||||
|                                     year="numeric" |  | ||||||
|                                   > |  | ||||||
|                                   </sl-format-date>`, |  | ||||||
|                               )} |                               )} | ||||||
|                             </span> |                         </span> | ||||||
|                           </div> |                       </div> | ||||||
|                         ` |                     `;
 | ||||||
|                       : nothing, |                   }, | ||||||
|                   () => html` <sl-skeleton></sl-skeleton> `, |                   () => html` <sl-skeleton></sl-skeleton> `, | ||||||
|                 )} |                 )} | ||||||
|                 <h5 class="mb-2 mt-4 text-xs leading-none text-neutral-500"> |                 <h5 class="mb-2 mt-4 text-xs leading-none text-neutral-500"> | ||||||
| @ -245,6 +258,12 @@ export class OrgSettingsBilling extends BtrixElement { | |||||||
|           `;
 |           `;
 | ||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|  |         case SubscriptionStatus.Trialing: { | ||||||
|  |           statusLabel = html` | ||||||
|  |             <span class="text-success-700">${msg("Trial")}</span> | ||||||
|  |           `;
 | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|         case SubscriptionStatus.PausedPaymentFailed: { |         case SubscriptionStatus.PausedPaymentFailed: { | ||||||
|           statusLabel = html` |           statusLabel = html` | ||||||
|             <span class="text-danger">${msg("Paused, payment failed")}</span> |             <span class="text-danger">${msg("Paused, payment failed")}</span> | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								frontend/src/replayWebPage.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								frontend/src/replayWebPage.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,6 @@ | |||||||
| /** | /** | ||||||
|  |  * @TODO Import from replaywebpage once https://github.com/webrecorder/replayweb.page/issues/376 is addressed
 | ||||||
|  |  * | ||||||
|  * @attr {String} source |  * @attr {String} source | ||||||
|  * @attr {String} coll |  * @attr {String} coll | ||||||
|  * @attr {String} config |  * @attr {String} config | ||||||
| @ -7,7 +9,9 @@ | |||||||
|  * @attr {String} noCache |  * @attr {String} noCache | ||||||
|  * @attr {String} url |  * @attr {String} url | ||||||
|  */ |  */ | ||||||
| class ReplayWebPage {} | class ReplayWebPage { | ||||||
|  |   fullReload(): void {} | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| declare global { | declare global { | ||||||
|   interface HTMLElementTagNameMap { |   interface HTMLElementTagNameMap { | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import { apiDateSchema } from "./api"; | |||||||
| 
 | 
 | ||||||
| export enum SubscriptionStatus { | export enum SubscriptionStatus { | ||||||
|   Active = "active", |   Active = "active", | ||||||
|  |   Trialing = "trialing", | ||||||
|   PausedPaymentFailed = "paused_payment_failed", |   PausedPaymentFailed = "paused_payment_failed", | ||||||
|   Cancelled = "cancelled", |   Cancelled = "cancelled", | ||||||
| } | } | ||||||
|  | |||||||
| @ -2947,7 +2947,7 @@ | |||||||
|               <x equiv-text="${daysDiff}" id="0"/> days</source> |               <x equiv-text="${daysDiff}" id="0"/> days</source> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s7fa0d24b94690373"> |       <trans-unit id="s7fa0d24b94690373"> | ||||||
|         <source>Your subscription ends on <x equiv-text="${this.localize.date(subscription!.futureCancelDate!, {
    month: "long",
    day: "numeric",
    year: "numeric",
    hour: "numeric",
})}" id="0"/>. Your user account, org, and all associated data will be deleted.</source> |         <source>Your subscription ends on <x equiv-text="${dateStr}" id="0"/>. Your user account, org, and all associated data will be deleted.</source> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="h16be212de6638b6c"> |       <trans-unit id="h16be212de6638b6c"> | ||||||
|         <source>We suggest downloading your archived items before they |         <source>We suggest downloading your archived items before they | ||||||
| @ -2961,11 +2961,7 @@ | |||||||
|         <source>Archiving will be disabled within one day</source> |         <source>Archiving will be disabled within one day</source> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s618b35a93b6fd392"> |       <trans-unit id="s618b35a93b6fd392"> | ||||||
|         <source>Your subscription ends on <x equiv-text="${this.localize.date(subscription!.futureCancelDate!, {
    month: "long",
    day: "numeric",
    year: "numeric",
    hour: "numeric",
})}" id="0"/>. You will no longer be able to run crawls, upload files, create browser profiles, or create collections.</source> |         <source>Your subscription ends on <x equiv-text="${dateStr}" id="0"/>. You will no longer be able to run crawls, upload files, create browser profiles, or create collections.</source> | ||||||
|       </trans-unit> |  | ||||||
|       <trans-unit id="hf17c5369da37401b"> |  | ||||||
|         <source>To keep your plan and continue crawling, see |  | ||||||
|                   <x equiv-text="${billingTabLink}" id="0"/>.</source> |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sfb85ab2a166e4c99"> |       <trans-unit id="sfb85ab2a166e4c99"> | ||||||
|         <source>Archiving is disabled for this org</source> |         <source>Archiving is disabled for this org</source> | ||||||
| @ -3671,7 +3667,7 @@ | |||||||
|         <source>The URL of the page to crawl.</source> |         <source>The URL of the page to crawl.</source> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s41d2278219615589"> |       <trans-unit id="s41d2278219615589"> | ||||||
|         <source>The crawler will visit and record each URL listed here. You can enter up to <x equiv-text="${this.localize.number(MAX_ADDITIONAL_URLS)}" id="0"/> URLs.</source> |         <source>The crawler will visit and record each URL listed here. You can enter up to <x equiv-text="${this.localize.number(URL_LIST_MAX_URLS)}" id="0"/> URLs.</source> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sfc5e402f8b21ef5f"> |       <trans-unit id="sfc5e402f8b21ef5f"> | ||||||
|         <source>If checked, the crawler will visit pages one link away.</source> |         <source>If checked, the crawler will visit pages one link away.</source> | ||||||
| @ -3787,10 +3783,6 @@ | |||||||
|       <trans-unit id="seb49ad0f81062f64"> |       <trans-unit id="seb49ad0f81062f64"> | ||||||
|         <source>Choose your preferred language for displaying Browsertrix in your browser.</source> |         <source>Choose your preferred language for displaying Browsertrix in your browser.</source> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="h746ce875ddd39a65"> |  | ||||||
|         <source>Your plan will be canceled on |  | ||||||
|                                   <x equiv-text="<sl-format-date class="truncate" date="${org.subscription.futureCancelDate}" month="long" day="numeric" year="numeric">
                                  </sl-format-date>" id="0"/></source> |  | ||||||
|       </trans-unit> |  | ||||||
|       <trans-unit id="h88cfbf4cb1b57616"> |       <trans-unit id="h88cfbf4cb1b57616"> | ||||||
|         <source>Deleting an org will delete all |         <source>Deleting an org will delete all | ||||||
|                   <x equiv-text="<strong class="font-semibold">
                    <sl-format-bytes value="${org.bytesStored}"></sl-format-bytes>
                  </strong>" id="0"/> |                   <x equiv-text="<strong class="font-semibold">
                    <sl-format-bytes value="${org.bytesStored}"></sl-format-bytes>
                  </strong>" id="0"/> | ||||||
| @ -3808,6 +3800,40 @@ | |||||||
|         <source>Profiles: |         <source>Profiles: | ||||||
|                     <x equiv-text="<sl-format-bytes value="${org.bytesStoredProfiles}"></sl-format-bytes>" id="0"/></source> |                     <x equiv-text="<sl-format-bytes value="${org.bytesStoredProfiles}"></sl-format-bytes>" id="0"/></source> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|  |       <trans-unit id="se3d7a30d5e45c393"> | ||||||
|  |         <source>Trial</source> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s582e36ff4a424786"> | ||||||
|  |         <source>Removing <x equiv-text="${this.localize.number(removeCount)} ${pluralOf("items", removeCount)}" id="0"/></source> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="s1f1b3cea8b3a20f3"> | ||||||
|  |         <source>You have <x equiv-text="${daysDiff}" id="0"/> days left of your Browsertrix trial</source> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="se4dfda71fd51327d"> | ||||||
|  |         <source>Your trial ends within one day</source> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="he8a019fc239da9d2"> | ||||||
|  |         <source>Your free trial ends on <x equiv-text="${dateStr}" id="0"/>. To continue using | ||||||
|  |                     Browsertrix, select <x equiv-text="<strong>" id="1"/>Choose Plan<x equiv-text="</strong>" id="2"/> in | ||||||
|  |                     <x equiv-text="${billingTabLink}" id="3"/>.</source> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="se5578c14db3c7b2b"> | ||||||
|  |         <source>Your web archives are always yours — you can download any archived items you'd like to keep | ||||||
|  |                   before the trial ends!</source> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="hc4152410e53b56c9"> | ||||||
|  |         <source>To choose a plan and continue using Browsertrix, see | ||||||
|  |                   <x equiv-text="${billingTabLink}" id="0"/>.</source> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="h003bd6a4e60ee0a5"> | ||||||
|  |         <source>Your trial will end on <x equiv-text="${futureCancelDate}" id="0"/> | ||||||
|  |                                   - Click <x equiv-text="<strong>" id="1"/>Choose Plan<x equiv-text="</strong>" id="2"/> to | ||||||
|  |                                   subscribe</source> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="h244d3ee006a72650"> | ||||||
|  |         <source>Your plan will be canceled on | ||||||
|  |                                 <x equiv-text="${futureCancelDate}" id="0"/></source> | ||||||
|  |       </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
| </xliff> | </xliff> | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user