Skip to content

Instantly share code, notes, and snippets.

@cayblood
Created March 17, 2023 03:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cayblood/ce6c536aa8d1180c8d545e2a3d4fb3eb to your computer and use it in GitHub Desktop.
Save cayblood/ce6c536aa8d1180c8d545e2a3d4fb3eb to your computer and use it in GitHub Desktop.
remix-forms problem
import React from "react";
import { type ActionFunction, json, type LoaderFunction } from "@remix-run/node";
import { requireUser } from "~/session.server";
import { getUserStatus, syncStripeCustomer } from "~/models/user.server";
import { useLoaderData } from "@remix-run/react";
import Gravatar from "~/components/Gravatar";
import { makeDomainFunction } from "domain-functions";
import { register } from "~/join.server";
import Form from "~/ui/form";
import { z } from "zod";
export const loader: LoaderFunction = async ({ request }) => {
let user = await requireUser(request, '/profile');
user = await syncStripeCustomer(user);
const status = await getUserStatus(user);
return json({
user,
status
});
}
const schema = z.object({
level: z.enum(['voting', 'reduced', 'basic', 'custom']).default('voting'),
frequency: z.enum(['annual', 'monthly']).default('annual'),
amount: z.number().default(200)
});
const mutation = makeDomainFunction(schema)(async (values) => {
console.log("values", values);
return values;
});
export const action: ActionFunction = async ({ request }) =>
register({
request,
schema,
mutation
});
export function Sidebar() {
return (
<div className="text-gray-500">
<p className="text text-base font-inter">
<strong>Your contact information will be kept confidential.</strong>
</p>
<p className="text text-base font-inter">
Basic membership is free. Members will receive email notifications when
new content is posted. You may cancel these notifications at any time.
Members who make an annual donation of $100 or more qualify for voting
membership and free admission to Association conferences. This
requirement is reduced to $40 for subscribers who are students, retired,
experiencing financial hardship, or who reside in a developing nation.
We also accept cryptocurrency or in-kind donations in lieu of cash.
Please <a href="mailto:contact@transfigurism.org">contact us</a> if
you would like to donate this way.
</p>
</div>
);
}
function MembershipLevelForm() {
const { status } = useLoaderData();
return (
<div className="flex flex-col">
<div className="w-full flex flex-col mt-12 p-4 sm:p-8 bg-gray-100 rounded-lg">
<div className="text text-lg font-bold font-inter mb-8 text-center text-gray-500 w-full">
Support the Mormon Transhumanist Association
</div>
<div className="flex flex-col md:flex-row">
<div className="md:hidden mb-6">
<Sidebar />
</div>
<div className="w-full md:w-1/2 self-start">
<Form schema={schema} values={{level: status}}>
{({ Field,
Button,
watch }) => {
const level = watch('level');
const frequency = watch('frequency');
const amount = watch('amount');
return (
<>
<Field name="level" label="Membership level" radio={true}>
{({ Label, RadioGroup, RadioWrapper, Radio }) => (
<>
<Label />
<RadioGroup>
<RadioWrapper>
<Radio value="voting" />
<Label>Voting ($100)</Label>
</RadioWrapper>
<RadioWrapper>
<Radio value="reduced" />
<Label>Voting (reduced) ($40)</Label>
</RadioWrapper>
<RadioWrapper>
<Radio value="basic" />
<Label>Basic</Label>
</RadioWrapper>
<RadioWrapper>
<Radio value="custom" />
<Label>Custom</Label>
</RadioWrapper>
</RadioGroup>
</>
)}
</Field>
{level.length > 0 && level === 'custom' && <Field name="frequency" label="Donation frequency" radio={true} />}
{level === 'custom' &&
<div className="flex flex-col">
<div className="flex flex-row">
<Field name="amount" label="Custom donation amount" />
<div className="ml-4 mb-2 self-end">
per {frequency === 'annual' ? 'year' : 'month'}
</div>
</div>
<div className="mt-4 text text-sm font-inter text-gray-500">
{typeof amount === 'number' && amount >= 100 && 'Instantly qualifies for voting membership.'}
{typeof amount === 'number' && amount >= 8.33 && amount < 100 && frequency === 'monthly' && `Qualifies for voting membership in ${Math.ceil(100 / amount)} months`}
{typeof amount === 'number' && amount < 100 && frequency === 'annual' && "Doesn't qualify for voting membership"}
{typeof amount === 'number' && amount < 8.33 && frequency === 'monthly' && "Doesn't qualify for voting membership"}
</div>
</div>
}
<Button disabled={level === status}>Update membership status</Button>
</>
)
}}
</Form>
</div>
<div className="hidden md:flex md:ml-12 md:w-1/2">
<Sidebar />
</div>
</div>
</div>
</div>
);
}
export default function Profile() {
const { user, status } = useLoaderData<typeof loader>();
const longStatus = (() => {
switch (status) {
case 'voting': return 'Voting member ($100 / year)';
case 'reduced': return 'Voting member (reduced) ($40 / year)';
case 'basic': return 'Basic member';
case 'custom': return 'Custom member';
}
})();
const paymentLink = new URL("https://buy.stripe.com/dR6cNZ1up5t45Hy8wz");
paymentLink.searchParams.set("prefilled_email", user.email);
return (
<>
<h1>Profile</h1>
<p className="text-base">
Thanks for joining the Mormon Transhumanist Association. We invite you to stay up-to-date with the
latest posts and content from the Association, and support our mission
to spread awareness of these ideas. Your support makes it possible to
hold events and create more of the quality content you see here.
</p>
<div className="mt-8 flex flex-row">
<div className="mr-8">
<a href="https://en.gravatar.com/emails/">
<Gravatar email={user.email} size={150} allowEdit={true} />
</a>
</div>
<div>
<table>
<tbody>
<tr>
<td className="text-right pr-4">Email:</td>
<td>{user.email}</td>
</tr>
<tr>
<td className="text-right pr-4">Membership status:</td>
<td>{longStatus}</td>
</tr>
<tr>
<td className="text-right pr-4">Full name:</td>
<td>{user.fullName}</td>
</tr>
<tr>
<td className="text-right pr-4">Preferred name:</td>
<td>{user.preferredName}</td>
</tr>
</tbody>
</table>
</div>
</div>
<MembershipLevelForm />
</>
);
}
@gustavoguichard
Copy link

Lemme suggest you to change the loader type signature to benefit from the Remix type inference:

export const loader = async ({ request }: LoaderFunctionArgs) => {
  // ...
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment