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

feat: add country selection to address component 🇨🇦 #434

Merged
merged 9 commits into from
Aug 22, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -54,32 +54,33 @@ export async function loader({ request }: LoaderFunctionArgs) {
});
}

const ClaimSwagPackFormData = ClaimSwagPackInput.omit({
studentId: true,
});

export async function action({ request }: ActionFunctionArgs) {
const session = await ensureUserAuthenticated(request);

const { data, errors, ok } = await validateForm(
request,
ClaimSwagPackFormData
ClaimSwagPackInput.omit({ studentId: true })
);

if (!ok) {
return json({ errors }, { status: 400 });
}

try {
await claimSwagPack({
const result = await claimSwagPack({
addressCity: data.addressCity,
addressCountry: data.addressCountry,
addressLine1: data.addressLine1,
addressLine2: data.addressLine2,
addressState: data.addressState,
addressZip: data.addressZip,
studentId: user(session),
});

if (!result.ok) {
return json({ error: result.error }, { status: result.code });
}

return redirect(Route['/home/claim-swag-pack/confirmation']);
} catch (e) {
reportException(e);
Expand All @@ -91,7 +92,7 @@ export async function action({ request }: ActionFunctionArgs) {
}
}

const keys = ClaimSwagPackFormData.keyof().enum;
const keys = ClaimSwagPackInput.keyof().enum;

export default function ClaimSwagPack() {
const { inventoryPromise } = useLoaderData<typeof loader>();
Expand Down Expand Up @@ -142,6 +143,19 @@ function ClaimSwagPackForm() {
<Text color="gray-500">Let us know where to send your swag pack!</Text>

<Address>
<Form.Field
error={errors.addressCountry}
label="Country"
labelFor={keys.addressCountry}
required
>
<Address.Country
id={keys.addressCountry}
name={keys.addressCountry}
required
/>
</Form.Field>

<Form.Field
error={errors.addressLine1}
label="Street Address"
Expand Down Expand Up @@ -176,7 +190,6 @@ function ClaimSwagPackForm() {
required
/>
</Form.Field>

<Form.Field
error={errors.addressState}
label="State"
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/modules/swag-pack/swag-pack.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export async function orderSwagPack(input: OrderSwagPackInput) {
shipping_address1: input.contact.address.line1,
shipping_address2: input.contact.address.line2,
shipping_city: input.contact.address.city,
shipping_country: 'US',
shipping_country: input.contact.address.country,
shipping_state: input.contact.address.state,
shipping_zip: input.contact.address.zip,
},
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/modules/swag-pack/swag-pack.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Address, Student } from '@oyster/types';

export const ClaimSwagPackInput = z.object({
addressCity: Address.shape.city,
addressCountry: Address.shape.country,
addressLine1: Address.shape.line1,
addressLine2: Address.shape.line2,
addressState: Address.shape.state,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,72 @@
import dedent from 'dedent';

import { db } from '@oyster/db';

import { job } from '@/infrastructure/bull/use-cases/job';
import { fail, type Result, success } from '@/shared/utils/core.utils';
import { orderSwagPack } from '../swag-pack.service';
import { type ClaimSwagPackInput } from '../swag-pack.types';

export async function claimSwagPack({
addressCity,
addressCountry,
addressLine1,
addressLine2,
addressState,
addressZip,
studentId,
}: ClaimSwagPackInput) {
}: ClaimSwagPackInput): Promise<Result> {
// We save the address regardless if the swag pack order failed or not so
// we'll be able to send them something in the future.
const student = await db
.selectFrom('students')
.select(['email', 'firstName', 'lastName'])
.updateTable('students')
.set({
addressCity,
addressCountry,
addressLine1,
addressLine2,
addressState,
addressZip,
})
.where('id', '=', studentId)
.returning(['email', 'firstName', 'lastName'])
.executeTakeFirstOrThrow();

// Currently, SwagUp only supports the US, but not Puerto Rico.
// See: https://support.swagup.com/en/articles/6952397-international-shipments-restricted-items
const isAddressSupported = addressCountry === 'US' && addressState !== 'PR';

// If the address isn't supported, then we'll send a notification to our
// team to create a gift card manually for them.
if (!isAddressSupported) {
const notification = dedent`
${student.firstName} ${student.lastName} (${student.email}) is attempting to claim a swag pack, but they're either from Puerto Rico or Canada, which is not supported for our product.

We let them know we'll send them a merch store gift card in the next "few days"!
`;

job('notification.slack.send', {
message: notification,
workspace: 'internal',
});

const error = dedent`
Unfortunately, our swag pack provider, SwagUp, does not support shipments to Puerto Rico and Canada. Instead, we will send you a gift card to our official merch store.

Our team has been notified, please give us a few days to complete this request!
`;

return fail({
code: 400,
error,
});
}

const swagPackOrderId = await orderSwagPack({
contact: {
address: {
city: addressCity,
country: addressCountry,
line1: addressLine1,
line2: addressLine2,
state: addressState,
Expand All @@ -35,14 +81,11 @@ export async function claimSwagPack({
await db
.updateTable('students')
.set({
addressCity,
addressLine1,
addressLine2,
addressState,
addressZip,
claimedSwagPackAt: new Date(),
swagUpOrderId: swagPackOrderId,
})
.where('id', '=', studentId)
.execute();

return success({});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type Kysely } from 'kysely';

export async function up(db: Kysely<any>) {
await db.schema
.alterTable('students')
.addColumn('address_country', 'text')
.execute();
}

export async function down(db: Kysely<any>) {
await db.schema
.alterTable('students')
.dropColumn('address_country')
.execute();
}
1 change: 1 addition & 0 deletions packages/types/src/domain/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type Entity = z.infer<typeof Entity>;

export const Address = z.object({
city: z.string().trim().min(1),
country: z.string().trim().min(2).max(3),
line1: z.string().trim().min(1),
line2: z.string().trim().optional(),
state: z.string().trim().min(1),
Expand Down
Loading