Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gifting #421

Merged
merged 5 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/assets/icons/gift.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/treasure-closed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/treasure.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion src/components/AmountEditable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,8 @@ export const AmountEditable: ParentComponent<{
}
}

function handleClose() {
function handleClose(e: SubmitEvent | MouseEvent | KeyboardEvent) {
e.preventDefault();
props.setAmountSats(BigInt(props.initialAmountSats));
setIsOpen(false);
setLocalSats(props.initialAmountSats);
Expand All @@ -576,6 +577,7 @@ export const AmountEditable: ParentComponent<{
)
);
props.exitRoute && navigate(props.exitRoute);
return false;
}

// What we're all here for in the first place: returning a value
Expand All @@ -586,6 +588,7 @@ export const AmountEditable: ParentComponent<{
satsToFiat(state.price, Number(localSats()) || 0, state.fiat)
);
setIsOpen(false);
return false;
}

function handleSatsInput(e: InputEvent) {
Expand Down Expand Up @@ -706,6 +709,7 @@ export const AmountEditable: ParentComponent<{
<div class="flex w-full justify-end">
<button
onClick={handleClose}
type="button"
class="h-8 w-8 rounded-lg hover:bg-white/10 active:bg-m-blue"
>
<img src={close} alt="Close" />
Expand Down
27 changes: 0 additions & 27 deletions src/components/CopyableQR.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/DetailsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const OVERLAY = "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm";
export const DIALOG_POSITIONER =
"fixed inset-0 z-50 flex items-center justify-center";
export const DIALOG_CONTENT =
"max-w-[500px] w-[90vw] max-h-[100dvh] overflow-y-scroll disable-scrollbars mx-4 p-4 bg-neutral-800/80 backdrop-blur-md shadow-xl rounded-xl border border-white/10";
"max-w-[500px] w-[90vw] max-h-[100dvh] overflow-y-scroll disable-scrollbars mx-4 p-4 bg-m-grey-800/75 backdrop-blur-md shadow-xl rounded-xl border border-white/10";

function LightningHeader(props: {
info: MutinyInvoice;
Expand Down
22 changes: 22 additions & 0 deletions src/components/GiftLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { A, useLocation } from "solid-start";

import gift from "~/assets/icons/gift.svg";
import { useI18n } from "~/i18n/context";

export function GiftLink() {
const i18n = useI18n();
const location = useLocation();

return (
<A
class="flex items-center gap-2 font-semibold text-m-red no-underline"
href="/settings/gift"
state={{
previous: location.pathname
}}
>
{i18n.t("settings.gift.give_sats_link")}
<img src={gift} class="h-5 w-5" alt="Gift" />
</A>
);
}
11 changes: 9 additions & 2 deletions src/components/IntegratedQR.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useI18n } from "~/i18n/context";
import { ReceiveFlavor } from "~/routes/Receive";
import { useCopy } from "~/utils";

function KindIndicator(props: { kind: ReceiveFlavor }) {
function KindIndicator(props: { kind: ReceiveFlavor | "gift" }) {
const i18n = useI18n();
return (
<div class="flex flex-col items-end text-black">
Expand All @@ -29,6 +29,13 @@ function KindIndicator(props: { kind: ReceiveFlavor }) {
<img src={boltBlack} alt="bolt" />
</Match>

<Match when={props.kind === "gift"}>
<h3 class="font-semibold">
{i18n.t("receive.integrated_qr.gift")}
</h3>
<img src={boltBlack} alt="bolt" />
</Match>

<Match when={props.kind === "unified"}>
<h3 class="font-semibold">
{i18n.t("receive.integrated_qr.unified")}
Expand Down Expand Up @@ -62,7 +69,7 @@ async function share(receiveString: string) {
export function IntegratedQr(props: {
value: string;
amountSats: string;
kind: ReceiveFlavor;
kind: ReceiveFlavor | "gift";
}) {
const i18n = useI18n();
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
Expand Down
29 changes: 29 additions & 0 deletions src/components/Logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Match, Switch } from "solid-js";

import pixelLogo from "~/assets/mutiny-pixel-logo.png";
import plusLogo from "~/assets/mutiny-plus-logo.png";
import { useMegaStore } from "~/state/megaStore";

export function Logo() {
const [state, _actions] = useMegaStore();
return (
<Switch>
<Match when={state.mutiny_plus}>
<img
id="mutiny-logo"
src={plusLogo}
class="h-[25px] w-[86px]"
alt="Mutiny Plus logo"
/>
</Match>
<Match when={true}>
<img
id="mutiny-logo"
src={pixelLogo}
class="h-[25px] w-[75px]"
alt="Mutiny logo"
/>
</Match>
</Switch>
);
}
40 changes: 40 additions & 0 deletions src/components/MutinyPlusCta.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ParentComponent } from "solid-js";
import { A } from "solid-start";

import forward from "~/assets/icons/forward.svg";
import { useI18n } from "~/i18n/context";

export const CtaCard: ParentComponent = (props) => {
return (
<div class="w-full">
<div class="relative">
<div class="to-bg-m-grey-900/50 absolute inset-0 h-full w-full scale-[0.60] transform rounded-full bg-m-red/50 bg-gradient-to-r from-m-red/50 blur-2xl" />
<div class="relative flex-col gap-2 rounded-xl border border-m-red bg-m-grey-800 py-4">
{props.children}{" "}
</div>
</div>
</div>
);
};

export function MutinyPlusCta() {
const i18n = useI18n();
return (
<CtaCard>
<A
href={"/settings/plus"}
class="flex w-full flex-col gap-1 px-4 py-2 no-underline hover:bg-m-grey-750 active:bg-m-grey-900"
>
<div class="flex justify-between">
<span>
Mutiny<span class="text-m-red">+</span>
</span>
<img src={forward} alt="go" />
</div>
<div class="text-sm text-m-grey-400">
{i18n.t("settings.plus.cta_description")}
</div>
</A>
</CtaCard>
);
}
6 changes: 4 additions & 2 deletions src/components/NWCBudgetEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export function NWCBudgetEditor(props: {
<TinyText>
{i18n.t("settings.connections.careful")}
</TinyText>
<KeyValue key={"Budget"}>
<KeyValue key={i18n.t("settings.connections.budget")}>
<Field name="budget_amount">
{(field, _fieldProps) => (
<div class="flex flex-col items-end gap-2">
Expand All @@ -129,7 +129,9 @@ export function NWCBudgetEditor(props: {
)}
</Field>
</KeyValue>
<KeyValue key={"Resets every"}>
<KeyValue
key={i18n.t("settings.connections.resets_every")}
>
<Field name="interval">
{(field, fieldProps) => (
<select
Expand Down
7 changes: 7 additions & 0 deletions src/components/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import airplane from "~/assets/icons/airplane.svg";
import receive from "~/assets/icons/big-receive.svg";
import mutiny_m from "~/assets/icons/m.svg";
import redshift from "~/assets/icons/rs.svg";
import scan from "~/assets/icons/scan.svg";
import settings from "~/assets/icons/settings.svg";
import userClock from "~/assets/icons/user-clock.svg";

Expand Down Expand Up @@ -67,6 +68,12 @@ export function NavBar(props: { activeTab: ActiveTab }) {
active={props.activeTab === "activity"}
alt="activity"
/>
<NavBarItem
href="/scanner"
icon={scan}
active={false}
alt="scan"
/>
<NavBarItem
href="/redshift"
icon={redshift}
Expand Down
4 changes: 3 additions & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export * from "./ChooseCurrency";
export * from "./ContactEditor";
export * from "./ContactForm";
export * from "./ContactViewer";
export * from "./CopyableQR";
export * from "./DecryptDialog";
export * from "./DeleteEverything";
export * from "./DetailsModal";
Expand All @@ -25,6 +24,7 @@ export * from "./IntegratedQR";
export * from "./JsonModal";
export * from "./KitchenSink";
export * from "./LoadingIndicator";
export * from "./Logo";
export * from "./Logs";
export * from "./MoreInfoModal";
export * from "./NavBar";
Expand All @@ -42,3 +42,5 @@ export * from "./TagEditor";
export * from "./Toaster";
export * from "./NostrActivity";
export * from "./SyncContactsForm";
export * from "./GiftLink";
export * from "./MutinyPlusCta";
5 changes: 4 additions & 1 deletion src/components/layout/BackPop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ export function BackPop() {

const state = location.state as StateWithPrevious;

const backPath = () => (state?.previous ? state?.previous : "/");
// If there's no previous state want to just go back one level, basically ../
const newBackPath = location.pathname.split("/").slice(0, -1).join("/");

const backPath = () => (state?.previous ? state?.previous : newBackPath);

return (
<BackButton
Expand Down
49 changes: 46 additions & 3 deletions src/i18n/en/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ export default {
integrated_qr: {
onchain: "On-chain",
lightning: "Lightning",
unified: "Unified"
unified: "Unified",
gift: "Lightning Gift"
},
remember_choice: "Remember my choice next time"
},
Expand Down Expand Up @@ -269,6 +270,7 @@ export default {
error_budget_zero: "Budget must be greater than zero",
add_connection: "Add Connection",
manage_connections: "Manage Connections",
manage_gifts: "Manage Gifts",
delete_connection: "Delete",
new_connection: "New Connection",
edit_connection: "Edit Connection",
Expand All @@ -292,7 +294,9 @@ export default {
"Be careful where you share this connection! Requests within budget will paid automatically.",
spent: "Spent",
remaining: "Remaining",
confirm_delete: "Are you sure you want to delete this connection?"
confirm_delete: "Are you sure you want to delete this connection?",
budget: "Budget",
resets_every: "Resets every"
},
emergency_kit: {
title: "Emergency Kit",
Expand Down Expand Up @@ -403,7 +407,9 @@ export default {
satisfaction: "Smug satisfaction",
gifting: "Gifting",
multi_device: "Multi-device access",
more: "... and more to come"
more: "... and more to come",
cta_description:
"Enjoy early access to new features and premium functionality."
},
restore: {
title: "Restore",
Expand Down Expand Up @@ -448,6 +454,43 @@ export default {
npub_label: "Nostr npub",
npub_required: "Npub can't be blank",
sync: "Sync"
},
gift: {
give_sats_link: "Give sats as a gift",
title: "Gifting",
receive_too_small:
"Your first receive needs to be {{amount}} SATS or greater.",
setup_fee_lightning:
"A lightning setup fee will be charged to receive this gift.",
already_claimed: "This gift has already been claimed",
sender_is_poor:
"The sender doesn't have enough balance to pay this gift.",
sender_generic_error: "Sender sent error: {{error}}",
receive_header: "You've been gifted some sats!",
receive_description:
"You must be pretty special. To claim your money just hit the big button. Funds will be added to your balance the next time your gifter is online.",
receive_claimed:
"Gift claimed! You should see the gift hit your balance shortly.",
receive_cta: "Claim Gift",
send_header: "Create Gift",
send_explainer:
"Give the gift of sats. Create a Mutiny gift URL that can be claimed by anyone with a web browser.",
send_name_required: "This is for your records",
send_name_label: "Recepient Name",
send_header_claimed: "Gift Received!",
send_claimed: "Your gift has been claimed. Thanks for sharing.",
send_sharable_header: "Sharable URL",
send_instructions:
"Copy this gift URL to your recipient, or ask them to scan this QR code with their wallet.",
send_another: "Create Another",
send_small_warning:
"A brand new Mutiny user won't be able to redeem fewer than 50k sats.",
send_cta: "Create a gift",
send_delete_button: "Delete Gift",
send_delete_confirm:
"Are you sure you want to delete this gift? Is this your rugpull moment?",
need_plus:
"Upgrade to Mutiny+ to enable gifting. Gifting allows you to create a Mutiny gift URL that can be claimed by anyone with a web browser."
}
},
swap: {
Expand Down
Loading