Skip to content

Commit

Permalink
Makes uploadOptions responsive to user premium status
Browse files Browse the repository at this point in the history
  • Loading branch information
allanlasser committed Dec 13, 2023
1 parent db8e77e commit 4f81496
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 63 deletions.
28 changes: 28 additions & 0 deletions src/api/fixtures/orgAndUser.fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,31 @@ export const organization = {
monthly_credit_allowance: 5000,
plan: "Organization",
};

export const proOrg = {
id: 4,
avatar_url:
"https://cdn.muckrock.com/media/account_images/allan-headshot-2016.jpg",
individual: true,
name: "lasser.allan",
slug: "lasserallan",
monthly_credits: 2500,
purchased_credits: 3000,
credit_reset_date: "2023-11-28",
monthly_credit_allowance: 2500,
plan: "Professional",
};

export const freeOrg = {
id: 4,
avatar_url:
"https://cdn.muckrock.com/media/account_images/allan-headshot-2016.jpg",
individual: true,
name: "lasser.allan",
slug: "lasserallan",
monthly_credits: 0,
purchased_credits: 0,
credit_reset_date: "2023-11-28",
monthly_credit_allowance: 0,
plan: "Free",
};
162 changes: 110 additions & 52 deletions src/common/UploadOptions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@
import type { Org, User } from "../pages/app/accounts/types.js";
import { getMe, getOrganization } from "../api/orgAndUser.js";
import { onMount } from "svelte";
import {
getUpgradeURL,
isOrgAdmin,
isPremiumOrg,
} from "../manager/orgsAndUsers.js";
import PremiumBadge from "../premium-credits/PremiumBadge.svelte";
export let language = defaultLanguage;
export let forceOcr = false;
export let ocrEngine = "tess4";
export let ocrEngine: "tess4" | "textract" = "tess4";
export let revisionControl = false;
let languageName = defaultLanguageName(languages);
Expand All @@ -23,25 +29,31 @@
async function getUser() {
try {
user = await getMe();
const activeOrg = user?.organization;
} catch (e) {
user = null;
console.error("Error setting User: ", JSON.stringify(e, null, 2));
}
try {
let activeOrg = user?.organization;
if (typeof activeOrg === "string") {
org = await getOrganization(activeOrg);
org = (await getOrganization(activeOrg)) as Org;
} else {
org = activeOrg;
}
} catch (e) {
user = null;
org = null;
console.error("Error setting Org: ", JSON.stringify(e, null, 2));
}
}
$: hasTextract = user?.feature_level > 0;
$: selectLanguages = ocrEngine === "textract" ? textractLanguages : languages;
$: hasTextract = isPremiumOrg(org);
// value, name, disabled
type OCREngine = [string, string, boolean];
type OCREngine = [value: string, name: string, isPremium: boolean];
const ocrEngines: OCREngine[] = [
["tess4", "Tesseract", false],
["textract", "Textract", !hasTextract],
["textract", "Textract", true],
];
function defaultLanguageName(languages) {
Expand All @@ -59,42 +71,55 @@
});
</script>

<div class="option">
<Select
name="document-language"
label={$_("uploadOptions.documentLang")}
placeholder={$_("uploadOptions.documentLanguagePlaceholder")}
options={selectLanguages}
bind:selected={languageName}
bind:value={language}
/>
</div>

<div class="option">
<div class="flex alignCenter">
<label>
{$_("uploadOptions.ocrEngine")}
<select name="ocr-engine" bind:value={ocrEngine}>
{#each ocrEngines as [value, name, disabled]}
<option {value} {disabled}>{name}</option>
{/each}
</select>
</label>
<label>
{$_("uploadOptions.forceOcr")}
<input type="checkbox" bind:checked={forceOcr} />
</label>
{#await getUserPromise then}
<div class="option">
<Select
name="document-language"
label={$_("uploadOptions.documentLang")}
placeholder={$_("uploadOptions.documentLanguagePlaceholder")}
options={selectLanguages}
bind:selected={languageName}
bind:value={language}
/>
</div>

<div class="small gray margin">
<p>{@html $_("uploadOptions.tesseract")}</p>
{#if !hasTextract}
<div class="option">
<div class="flex alignCenter">
<label>
{$_("uploadOptions.ocrEngine")}
<select name="ocr-engine" bind:value={ocrEngine}>
{#each ocrEngines as [value, name, isPremium]}
<option {value} disabled={isPremium && !isPremiumOrg(org)}
>{name}</option
>
{/each}
</select>
</label>
<label>
{$_("uploadOptions.forceOcr")}
<input type="checkbox" bind:checked={forceOcr} />
</label>
</div>

<div class="small gray margin">
<p>{@html $_("uploadOptions.tesseract")}</p>
<p class="nomargin">
{@html $_("uploadOptions.textract")}
<span>{@html $_("uploadOptions.textract")}</span>
{#if !isPremiumOrg(org)}
{#if isOrgAdmin(user)}
<span
>{@html $_("uploadOptions.premiumToutAdmin", {
values: { upgradeUrl: getUpgradeURL(org) },
})}</span
>
{:else}
<span class="nomargin"
>{@html $_("uploadOptions.premiumToutMember")}</span
>
{/if}
{/if}
</p>
<p class="nomargin">{@html $_("uploadOptions.premiumTout")}</p>
{:else}
{#await getUserPromise then}
{#if isPremiumOrg(org) && ocrEngine == "textract"}
<p>
{$_("uploadOptions.creditHelpText", {
values: {
Expand All @@ -103,21 +128,46 @@
},
})}
</p>
{/await}
{/if}
{/if}
</div>
</div>
</div>

<div class="option">
<div class="middle">
<label>
{$_("uploadOptions.revisionControl")}
<input type="checkbox" bind:checked={revisionControl} />
</label>
<p class="small gray nomargin">{$_("uploadOptions.revisionControlHelp")}</p>
<p class="small gray nomargin">{@html $_("uploadOptions.premiumTout")}</p>

<div class="option">
<div class="middle">
<div class="flex spaceBetween">
<div>
<label class:fade={!isPremiumOrg(org)}>
{$_("uploadOptions.revisionControl")}
<input
type="checkbox"
bind:checked={revisionControl}
disabled={!isPremiumOrg(org)}
/>
</label>
<p class="small gray nomargin" class:fade={!isPremiumOrg(org)}>
{$_("uploadOptions.revisionControlHelp")}
</p>
</div>
<PremiumBadge />
</div>
{#if !isPremiumOrg(org)}
<p class="small gray nomargin">
{#if isOrgAdmin(user)}
<span
>{@html $_("uploadOptions.premiumToutAdmin", {
values: { upgradeUrl: getUpgradeURL(org) },
})}</span
>
{:else}
<span class="nomargin"
>{@html $_("uploadOptions.premiumToutMember")}</span
>
{/if}
</p>
{/if}
</div>
</div>
</div>
{/await}

<style>
.margin {
Expand All @@ -132,6 +182,9 @@
.gray {
color: var(--darkgray);
}
.fade {
opacity: 0.7;
}
.middle {
display: inline-block;
vertical-align: middle;
Expand All @@ -144,12 +197,17 @@
display: flex;
width: 100%;
gap: 1rem;
align-items: flex-start;
}
.alignCenter {
align-items: center;
}
.spaceBetween {
justify-content: space-between;
}
label {
font-weight: bold;
}
Expand Down
27 changes: 25 additions & 2 deletions src/common/stories/UploadOptions.stories.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
export const meta = {
title: "Common / Upload Options",
component: UploadOptions,
parameters: { layout: "centered" },
parameters: {
layout: "centered",
chromatic: { delay: 300 },
},
};
</script>

Expand All @@ -22,7 +25,27 @@
</Template>

<Story
name="Default"
name="As Free Org Member"
{args}
parameters={{ msw: { handlers: [mockGetMe.freeOrgMember, mockGetOrg.free] } }}
/>
<Story
name="As Free Org Admin"
{args}
parameters={{ msw: { handlers: [mockGetMe.freeOrgAdmin, mockGetOrg.free] } }}
/>
<Story
name="As Premium Org Member"
{args}
parameters={{ msw: { handlers: [mockGetMe.data, mockGetOrg.data] } }}
/>
<Story
name="As Free User"
{args}
parameters={{ msw: { handlers: [mockGetMe.freeUser, mockGetOrg.free] } }}
/>
<Story
name="As Pro User"
{args}
parameters={{ msw: { handlers: [mockGetMe.proUser] } }}
/>
5 changes: 3 additions & 2 deletions src/langs/json/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -355,11 +355,12 @@
"ocrEngine": "OCR Engine:",
"tesseract": "<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://tesseract-ocr.github.io/tessdoc/\">Tesseract</a> is an open source OCR engine available free to all DocumentCloud accounts.",
"textract": "<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://aws.amazon.com/textract/\">Textract</a> is a more powerful OCR engine that can better handle messy text, handwriting and fuzzy scans.",
"premiumTout": "It is a premium feature available on <strong>Professional</strong> and <strong>Organization</strong> plans. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.documentcloud.org/premium/\">Learn more</a> or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://accounts.muckrock.com/accounts/signup/?intent=documentcloud\">sign up</a>.",
"premiumToutAdmin": "This premium feature is available on <strong>Professional</strong> and <strong>Organization</strong> plans. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"{upgradeUrl}\">Upgrade today!</a>",
"premiumToutMember": "Contact your organization's admin about upgrading to an <strong>Organization</strong> plan.",
"creditHelpText": "Selecting Textract will cost one AI Credit per page in the document. If you go over, the document will fall back to using Tesseract. You are currently working on behalf of {organization} and have {n, plural, one {# credit} other {# credits}} left.",
"documentLanguagePlaceholder": "Begin typing to see options",
"revisionControl": "Revision Control:",
"revisionControlHelp": "Make previous versions of the document will be preserved and available for download."
"revisionControlHelp": "All previous versions to the document will be preserved and available for download."
},
"actionBar": {
"selectDocs": "Select some documents to reveal edit actions",
Expand Down
15 changes: 8 additions & 7 deletions src/manager/orgsAndUsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,16 +239,17 @@ export function getCreditBalance(org) {
return org.monthly_credits + org.purchased_credits;
}

export async function triggerPremiumUpgradeFlow(org) {
let url;
export function getUpgradeURL(org) {
if (org.individual) {
// Redirect the user to their Squarelet account settings
url = SQUARELET_URL + `/users/~payment/`;
} else {
// Redirect the user to the Squarelet organization settings
url = SQUARELET_URL + `/organizations/${org.slug}/payment/`;
return SQUARELET_URL + `/users/~payment/`;
}
window?.open(url);
// Redirect the user to the Squarelet organization settings
return SQUARELET_URL + `/organizations/${org.slug}/payment/`;
}

export async function triggerPremiumUpgradeFlow(org) {
window?.open(getUpgradeUrl(org));
}

// TODO: Handle flow for purchasing premium credits (#342)
Expand Down
20 changes: 20 additions & 0 deletions src/pages/app/accounts/stories/mockData.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
import { rest } from "msw";
import { baseApiUrl } from "../../../../api/base.js";
import {
freeOrg,
me,
organizations,
proOrg,
users,
} from "../../../../api/fixtures/orgAndUser.fixtures.js";

/* Mock Request Handlers */
const mockMeUrl = new URL(`users/me/`, baseApiUrl).toString();
export const mockGetMe = {
data: rest.get(mockMeUrl, (req, res, ctx) => res(ctx.json(me))),
freeUser: rest.get(mockMeUrl, (req, res, ctx) =>
res(
ctx.json({
...me,
organization: freeOrg,
admin_organizations: [...me.admin_organizations, freeOrg.id],
}),
),
),
proUser: rest.get(mockMeUrl, (req, res, ctx) =>
res(
ctx.json({
...me,
organization: proOrg,
admin_organizations: [...me.admin_organizations, proOrg.id],
}),
),
),
noOrgs: rest.get(mockMeUrl, (req, res, ctx) =>
res(ctx.json({ ...me, organization: "4" })),
),
Expand Down

0 comments on commit 4f81496

Please sign in to comment.