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

add orders #109

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion src/extensions/arcade/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import "./slack/index.js";
import "./slack/sessions.js";
import "./slack/scrapbook.js";
import "./slack/shop.js";
import "./slack/walkthrough.js"
import "./slack/walkthrough.js";
import "./slack/orders.js";

import { emitter } from "../../lib/emitter.js";

emitter.on('init', () => {
Expand Down
38 changes: 38 additions & 0 deletions src/extensions/arcade/slack/orders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AirtableAPI } from "../../../lib/airtable.js";
import { Slack } from "../../../lib/bolt.js";
import { Actions } from "../../../lib/constants.js";
import { prisma } from "../../../lib/prisma.js";
import { t } from "../../../lib/templates.js";
import { Loading } from "../../slack/views/loading.js";
import { Orders } from "./views/orders.js";

Slack.action(Actions.ORDERS, async ({ body, client }) => {
const view = await client.views.push({
trigger_id: (body as any).trigger_id,
view: Loading.loading(),
});

const user = await prisma.user.findFirst({
where: {
slackUser: {
slackId: body.user.id,
}
}
});

if (!user || !user.metadata.airtable) {
await Slack.views.update({
view_id: view.view?.id,
view: Loading.error(t('error.not_a_user'))
});

return;
}

const orders = await AirtableAPI.Orders.findByUser(user.metadata.airtable.id);

await Slack.views.update({
view_id: view.view?.id,
view: Orders.order(orders)
});
});
2 changes: 2 additions & 0 deletions src/extensions/arcade/slack/shop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Slack.command(Commands.SHOP, async ({ command }) => {
awaitingApproval: Math.floor(airtableUser.fields["Minutes (Pending Approval)"] / 60),
inOrders: Math.floor(airtableUser.fields["In Pending (Minutes)"] / 60),
spent: Math.floor(airtableUser.fields["Spent Fulfilled (Minutes)"] / 60),
verification: airtableUser.fields["Verification Status (from YSWS Verification User)"] ?? ""
})
})
});
Expand Down Expand Up @@ -78,6 +79,7 @@ Slack.action(Actions.OPEN_SHOP, async ({ body }) => {
awaitingApproval: Math.floor(airtableUser.fields["Minutes (Pending Approval)"] / 60),
inOrders: Math.floor(airtableUser.fields["In Pending (Minutes)"] / 60),
spent: Math.floor(airtableUser.fields["Spent Fulfilled (Minutes)"] / 60),
verification: airtableUser.fields["Verification Status (from YSWS Verification User)"] ?? ""
})
})
});
54 changes: 54 additions & 0 deletions src/extensions/arcade/slack/views/orders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { KnownBlock, View } from "@slack/bolt";
import { AirtableOrdersRead } from "../../../../lib/airtable.js";

export class Orders {
public static order(orders: AirtableOrdersRead[]): View {
const blocks: KnownBlock[] = [];

if (orders.length === 0) {
blocks.push({
type: "section",
text: {
type: "mrkdwn",
text: "You have no orders."
}
});
} else {
for (const order of orders) {
blocks.push({
type: "section",
text: {
type: "mrkdwn",
text:
`*${order["Item: Name"]}*
_Status:_ ${order["Status"]}
_Quantity:_ ${order["Quantity"]}
_Price:_ ${Math.floor(order["Order Price (Minutes)"] / 60)} :tw_admission_tickets:`
},
accessory: order["Item: Image"][0] ? {
type: "image",
image_url: order["Item: Image"][0],
alt_text: order["Item: Name"][0],
} : undefined
}, {
type: "divider",
});
}
}

return {
type: "modal" as const,
title: {
type: "plain_text" as const,
text: "orders",
emoji: true,
},
close: {
type: "plain_text" as const,
text: "close",
emoji: true,
},
blocks,
};
}
}
17 changes: 14 additions & 3 deletions src/extensions/arcade/slack/views/shop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export class Shop {
awaitingApproval,
inOrders,
spent,
verification
// lifetime,
// lifetimeTickets
}: {
Expand All @@ -16,6 +17,7 @@ export class Shop {
awaitingApproval: number,
inOrders: number,
spent: number,
verification: string
}): View {
return {
"type": "modal",
Expand Down Expand Up @@ -67,7 +69,7 @@ _How do I get tickets?_\n- Provide <https://hackclub.slack.com/canvas/C077TSWKER
"type": "section",
"text": {
"type": "mrkdwn",
"text": `*Tickets spent:* ${spent + inOrders}`
"text": `*Tickets spent:* ${spent + inOrders} :tw_admission_tickets:`
}
},
{
Expand All @@ -83,7 +85,7 @@ _How do I get tickets?_\n- Provide <https://hackclub.slack.com/canvas/C077TSWKER
"text": "open the shop!",
"emoji": true
},
"url": `${Environment.SHOP_URL}/arcade/${recordId}/shop/`,
"url": verification ? `${Environment.SHOP_URL}/arcade/${recordId}/shop/` : `https://forms.hackclub.com/eligibility`,
"action_id": Actions.NO_ACTION,
"style": "primary"
},
Expand All @@ -95,7 +97,16 @@ _How do I get tickets?_\n- Provide <https://hackclub.slack.com/canvas/C077TSWKER
"emoji": true
},
"action_id": Actions.SESSIONS,
}
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "view orders",
"emoji": true
},
"action_id": Actions.ORDERS,
}
]
},
// {
Expand Down
30 changes: 30 additions & 0 deletions src/lib/airtable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ dotenv.config();

import Airtable from "airtable";
import { emitter } from './emitter.js';
import { Orders } from '../extensions/arcade/slack/views/orders.js';

Airtable.configure({
apiKey: process.env.AIRTABLE_TOKEN
Expand All @@ -17,6 +18,7 @@ const users = base("Users");
const sessions = base("Sessions");
const scrapbooks = base("Scrapbook");
const api = base("API");
const orders = base("Orders");

type AirtableRecordID = string;

Expand Down Expand Up @@ -67,6 +69,8 @@ type AirtableUserRead = {
"Minutes (Rejected)": number,
// "Preexisting": boolean,
"API Authorization": boolean,

"Verification Status (from YSWS Verification User)": "Unknown" | "Not Eligible" | "Eligible L1" | "Eligible L2" | "Testing" | "",
};

type AirtableSessionWrite = {
Expand Down Expand Up @@ -124,6 +128,16 @@ type AirtableAPIRead = {
"Active": boolean,
};

export type AirtableOrdersRead = {
"User": [AirtableRecordID],
"Item: Name": [string],
"Item: Image": [string],
"Status": "Awaiting Fulfillment" | "Fulfilled" | "Declined (Insufficient balance)" | "Declined (Failed age verification)" | "Declined (Other reason)" | "Incomplete Fillout Submission"
"Quantity": number,
"Order Price (Minutes)": number,
"User: Record ID": AirtableRecordID,
}

export const AirtableAPI = {
User: {
async find(record: string): Promise<{id: AirtableRecordID, fields: AirtableUserRead} | null> {
Expand Down Expand Up @@ -353,6 +367,22 @@ export const AirtableAPI = {
// Return a list of endpoints
return records.map(record => ({id: record.id, fields: record.fields as AirtableAPIRead}));
}
},
Orders: {
async findByUser(user: AirtableRecordID): Promise<AirtableOrdersRead[]> {
console.log(`[AirtableAPI.Orders.findByUser] Finding orders for ${user}`)

const now = Date.now();

const records = await orders.select({
filterByFormula: `{User: Record ID} = "${user}"`
}).all()
.catch(error => { console.error(error); return [] });

console.log(`[AirtableAPI.Orders.findByUser] Took ${Date.now() - now}ms`)

return records.map(record => record.fields as AirtableOrdersRead);
}
}
};

Expand Down
2 changes: 2 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export const Actions = {

SESSIONS_PREVIOUS: 'sessionsprevious',
SESSIONS_NEXT: 'sessionsnext',

ORDERS: 'orders',
};

export const Callbacks = {
Expand Down