From 1463c05731eb31cb0f4f4f0c39f4d7d0f11e28df Mon Sep 17 00:00:00 2001 From: Pijus Kamandulis Date: Sat, 12 Oct 2024 00:04:36 +0300 Subject: [PATCH] Implement session invites --- .env | 1 + appwrite.json | 109 +++++++++++++ functions/EstimationSessionInvite/.gitignore | 148 ++++++++++++++++++ functions/EstimationSessionInvite/README.md | 39 +++++ functions/EstimationSessionInvite/bun.lockb | Bin 0 -> 3863 bytes functions/EstimationSessionInvite/env.d.ts | 10 ++ .../EstimationSessionInvite/package-lock.json | 88 +++++++++++ .../EstimationSessionInvite/package.json | 18 +++ .../src/definitions.d.mts | 49 ++++++ functions/EstimationSessionInvite/src/main.ts | 148 ++++++++++++++++++ .../EstimationSessionInvite/tsconfig.json | 5 + src/lib/appwrite.ts | 5 +- src/lib/functions/estimationSessionInvite.ts | 88 +++++++++++ src/main.tsx | 8 + src/pages/Join.tsx | 79 ++++++++++ src/vite-env.d.ts | 1 + 16 files changed, 795 insertions(+), 1 deletion(-) create mode 100644 appwrite.json create mode 100644 functions/EstimationSessionInvite/.gitignore create mode 100644 functions/EstimationSessionInvite/README.md create mode 100755 functions/EstimationSessionInvite/bun.lockb create mode 100644 functions/EstimationSessionInvite/env.d.ts create mode 100644 functions/EstimationSessionInvite/package-lock.json create mode 100644 functions/EstimationSessionInvite/package.json create mode 100644 functions/EstimationSessionInvite/src/definitions.d.mts create mode 100644 functions/EstimationSessionInvite/src/main.ts create mode 100644 functions/EstimationSessionInvite/tsconfig.json create mode 100644 src/lib/functions/estimationSessionInvite.ts create mode 100644 src/pages/Join.tsx diff --git a/.env b/.env index 02409f9..dab6b2f 100644 --- a/.env +++ b/.env @@ -2,3 +2,4 @@ VITE_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 VITE_APPWRITE_PROJECT_ID=scrummie-poker VITE_APPWRITE_DATABASE_ID=670402eb000f5aff721f VITE_APPWRITE_ESTIMATION_SESSION_COLLECTION_ID=670402f60023cb78d441 +VITE_SESSION_INVITE_FUNCTION_ID=6708356a001290606744 diff --git a/appwrite.json b/appwrite.json new file mode 100644 index 0000000..8dfb5a6 --- /dev/null +++ b/appwrite.json @@ -0,0 +1,109 @@ +{ + "projectId": "scrummie-poker", + "projectName": "ScrummiePoker", + "settings": { + "services": { + "account": true, + "avatars": true, + "databases": true, + "locale": true, + "health": true, + "storage": true, + "teams": true, + "users": true, + "functions": true, + "graphql": true, + "messaging": true + }, + "auth": { + "methods": { + "jwt": true, + "phone": true, + "invites": true, + "anonymous": true, + "email-otp": true, + "magic-url": true, + "email-password": true + }, + "security": { + "duration": 31536000, + "limit": 0, + "sessionsLimit": 10, + "passwordHistory": 0, + "passwordDictionary": false, + "personalDataCheck": false, + "sessionAlerts": false, + "mockNumbers": [] + } + } + }, + "functions": [ + { + "$id": "6708356a001290606744", + "execute": ["users"], + "name": "EstimationSessionInvite", + "enabled": true, + "logging": true, + "runtime": "bun-1.0", + "scopes": ["users.read", "documents.read", "documents.write"], + "events": [], + "schedule": "", + "timeout": 15, + "entrypoint": "src/main.ts", + "commands": "bun install", + "path": "functions/EstimationSessionInvite" + } + ], + "databases": [ + { + "$id": "670402eb000f5aff721f", + "name": "estimations", + "enabled": true + } + ], + "collections": [ + { + "$id": "670402f60023cb78d441", + "$permissions": ["create(\"users\")"], + "databaseId": "670402eb000f5aff721f", + "name": "sessions", + "enabled": true, + "documentSecurity": true, + "attributes": [ + { + "key": "userId", + "type": "string", + "required": true, + "array": false, + "size": 50, + "default": null + }, + { + "key": "name", + "type": "string", + "required": true, + "array": false, + "size": 200, + "default": null + }, + { + "key": "tickets", + "type": "string", + "required": false, + "array": true, + "size": 100, + "default": null + }, + { + "key": "sessionState", + "type": "string", + "required": false, + "array": false, + "size": 1000, + "default": null + } + ], + "indexes": [] + } + ] +} diff --git a/functions/EstimationSessionInvite/.gitignore b/functions/EstimationSessionInvite/.gitignore new file mode 100644 index 0000000..42cbc54 --- /dev/null +++ b/functions/EstimationSessionInvite/.gitignore @@ -0,0 +1,148 @@ +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +# End of https://www.toptal.com/developers/gitignore/api/node + +# OS +## Mac +.DS_Store + +# Directory used by Appwrite CLI for local development +.appwrite \ No newline at end of file diff --git a/functions/EstimationSessionInvite/README.md b/functions/EstimationSessionInvite/README.md new file mode 100644 index 0000000..1501a3b --- /dev/null +++ b/functions/EstimationSessionInvite/README.md @@ -0,0 +1,39 @@ +# Estimation Session Invite Function + +This function allows other users to join an existing estimation session by providing it's Id. + +## Usage + +### GET /?action=get-info&estimationId=[session-id] + +- Gets session's information by id + +**Response** + +Sample `200` Response: + +```json +{ + "id": "session-id", + "name": "session name" +} +``` + +### GET /?action=join&estimationId=[session-id] + +- Joins session by id + +**Response** + +Sample `200` Response: + +```json +{ + "message": "Estimation session joined" +} +``` + +## Environment Variables + +- APPWRITE_DATABASE_ID - Database Id +- APPWRITE_ESTIMATION_SESSION_COLLECTION_ID - Sessions collection Id diff --git a/functions/EstimationSessionInvite/bun.lockb b/functions/EstimationSessionInvite/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..183cf6e908861328887daabd9cf65995cf7108db GIT binary patch literal 3863 zcmd^C3rrMO6rIIIK7t=aP0-p!2!3#8KP<2U1(6b~wpOL}gGg9lmK{FJIx{RHtx%(Y z3IgjV#F`KV)M&LqgWsx9E0qE&))1{g{Ge1VS|bLny7s=^dAK%;G-%SKcQbq6n|IH7 z_uY5j%pDJRX&T2$V;P;4))^$RX*x%^$mG7ts~NXvx7TO5e$r%IdO#9B^4k>?vld8m2ZIragGCYc|Ipb&pT-8}{!oI*_JEc% zXb$uk(0Z2Ud4^`)pby5`nIeK11iBiu6KEDRx(`^w5wy=7ug%&{V&@uV{pk9nsC5fx zj+;=kq_nCfS~hRL_mP6kKN1@AT<(r5mPQtMRedzYAvLJ>&fG1>%IZrpN)sl_jjvvT!XU=` z!^DBY{K$u~eF1|F#^(Si0PwDG5B7@?6638b7~cT+P{7*)0_6bGeG!azhK3UGC<74| z`Op`^_)uv0S*;&(ug@X|OLU>QVZW_4ngi`A8n-?nsE-Z?(n#J7k7A1bW_;Z z<0myw*BorDDY$1_T4z5pc4fn-R=jv0%yDS1U9a+9^U62HomY>nR@b|14k;7Y1>JDiF@03~Eh}Dh7O;rdC50)S z2e+?n2t1Lw>95w%qOHIAG&(QpTrlJ0(|q?l|7J!}D!VkS)2T~omvin()2?W*4%14h z-?Wx5({}d3Aubo5KKZlc$g~&(Q8c-=Ec}%-otC%=gLlticfLDg(Zw~K=5YU^reyBTda5o zVrLPr*PQ&oj&1xj$K7+6ZrJ|e{A}0xTA$9-fej(o9vS#O{<|vOZ_U{>yU|m6d{xP! z4BIGa-dFpL?%mf;&KjY)v%K7j7v(h;aoev=#^)a&oO=80LWTFZpz@@F@uI?E6>Z0+ zo-HgobHy0hJ-=0yyClX??L2bC-VMW29-U~8URaYBHYqR6^P;@iiWi>~#4SGGcIsKy zv&uuyJ?q>?7pFfiY_VH)b5(hJVAJHH@WKcByALX&obQS2#XBQ%$JcE=TDAL;VPBW; zwa<)Y;&~IuT;T1Gpxngor~A-G@2~#wy)SG(m-)Wj`v39n63U$)1nn5av2hfm<9Ky) zGKFg`>I;^pgCz=?gkzS|shY_Wr9_>~BURVUR(Ruh?ptH9O6OKV?K8j#K zy#bH%0p%`w2BPN*dIq597CN&kfg`4mTxfoTMO>H{@nBx0(Rh)-w^4Y3ItDFP_$^6O z!8-kV{FJ|3C$8skJ!UV8mQ(PPo2vj=a5k>w?0a4j_$=aD4=_Fy{0PDWJU~??t_p2{ zO$l@|0@s%Xlft(E`W!HKbgA()sn+Y$ zS%#;zJg?^hq*9hnU^t%5pmh3_M2<>j6Qr-$Ny%6Hq$U0opRn9)r=j^cEvZxUjDaT8 z8D2}O6KEa(Hu^Wf@N91wCe3?{=0@K;IBlr7M6Q?luo-aUCTUJD#M29|CvE`jX@du7 zj}cUea5^V6P&j&_v$Ddgv?Lz{6xh;q@k|_J*{EJvtj&PNx=Wff*7q%|>hT)T=^SZ3 W8H&DPAP7PzEGHVFEJxwL_y0F;iR)hg literal 0 HcmV?d00001 diff --git a/functions/EstimationSessionInvite/env.d.ts b/functions/EstimationSessionInvite/env.d.ts new file mode 100644 index 0000000..025528b --- /dev/null +++ b/functions/EstimationSessionInvite/env.d.ts @@ -0,0 +1,10 @@ +declare module 'bun' { + interface Env { + APPWRITE_FUNCTION_API_ENDPOINT: string; + APPWRITE_FUNCTION_PROJECT_ID: string; + APPWRITE_DATABASE_ID: string; + APPWRITE_ESTIMATION_SESSION_COLLECTION_ID: string; + } +} + +export {}; diff --git a/functions/EstimationSessionInvite/package-lock.json b/functions/EstimationSessionInvite/package-lock.json new file mode 100644 index 0000000..7329c25 --- /dev/null +++ b/functions/EstimationSessionInvite/package-lock.json @@ -0,0 +1,88 @@ +{ + "name": "estimation-session-invite", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "estimation-session-invite", + "version": "1.0.0", + "dependencies": { + "node-appwrite": "^14.1.0" + }, + "devDependencies": { + "@types/bun": "^1.1.11", + "prettier": "^3.3.3" + } + }, + "node_modules/@types/bun": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.1.11.tgz", + "integrity": "sha512-0N7D/H/8sbf9JMkaG5F3+I/cB4TlhKTkO9EskEWP8XDr8aVcDe4EywSnU4cnyZy6tar1dq70NeFNkqMEUigthw==", + "dev": true, + "dependencies": { + "bun-types": "1.1.30" + } + }, + "node_modules/@types/node": { + "version": "20.12.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz", + "integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/bun-types": { + "version": "1.1.30", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.30.tgz", + "integrity": "sha512-mGh7NLisOXskBU62DxLS+/nwmLlCYHYAkCzdo4DZ9+fzrpP41hAdOqaN4DO6tQfenHb4pYb0/shw29k4/6I2yQ==", + "dev": true, + "dependencies": { + "@types/node": "~20.12.8", + "@types/ws": "~8.5.10" + } + }, + "node_modules/node-appwrite": { + "version": "14.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "node-fetch-native-with-agent": "1.7.2" + } + }, + "node_modules/node-fetch-native-with-agent": { + "version": "1.7.2", + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/functions/EstimationSessionInvite/package.json b/functions/EstimationSessionInvite/package.json new file mode 100644 index 0000000..67c201a --- /dev/null +++ b/functions/EstimationSessionInvite/package.json @@ -0,0 +1,18 @@ +{ + "name": "estimation-session-invite", + "version": "1.0.0", + "description": "", + "main": "src/main.ts", + "type": "module", + "scripts": { + "format": "prettier --write ." + }, + "dependencies": { + "node-appwrite": "^14.1.0" + }, + "devDependencies": { + "@types/bun": "^1.1.11", + "bun-types": "^1.1.30", + "prettier": "^3.3.3" + } +} diff --git a/functions/EstimationSessionInvite/src/definitions.d.mts b/functions/EstimationSessionInvite/src/definitions.d.mts new file mode 100644 index 0000000..619c18d --- /dev/null +++ b/functions/EstimationSessionInvite/src/definitions.d.mts @@ -0,0 +1,49 @@ +declare type AppwriteRequest = { + bodyRaw: string; + body: Object; + headers: Object; + scheme: string; + method: string; + url: string; + host: string; + port: number; + path: string; + queryString: string; + query: Object; +}; + +declare type AppwriteSendReturn = { + body: any; + statusCode: number; + headers: Object; +}; + +declare type AppwriteResponse = { + empty: () => AppwriteSendReturn; + json: (obj: any, statusCode?: number, headers?: Object) => AppwriteSendReturn; + text: (text: string) => AppwriteSendReturn; + redirect: ( + url: string, + statusCode?: number, + headers?: Object, + ) => AppwriteSendReturn; + send: ( + body: any, + statusCode?: number, + headers?: Object, + ) => AppwriteSendReturn; +}; + +declare type AppwriteRuntimeContext = { + req: AppwriteRequest; + res: AppwriteResponse; + log: (message: any) => void; + error: (message: any) => void; +}; + +export { + AppwriteRequest, + AppwriteSendReturn, + AppwriteResponse, + AppwriteRuntimeContext, +}; diff --git a/functions/EstimationSessionInvite/src/main.ts b/functions/EstimationSessionInvite/src/main.ts new file mode 100644 index 0000000..99d3013 --- /dev/null +++ b/functions/EstimationSessionInvite/src/main.ts @@ -0,0 +1,148 @@ +import { Client, Databases } from 'node-appwrite'; +import { AppwriteRuntimeContext, AppwriteSendReturn } from './definitions.mjs'; + +const joinSession = async ({ + client, + estimationId, + userId, + ctx: { error, res }, +}: { + client: Client; + estimationId: string; + userId: string; + ctx: AppwriteRuntimeContext; +}): Promise => { + const databases = new Databases(client); + + let estimation; + try { + estimation = await databases.getDocument( + Bun.env.APPWRITE_DATABASE_ID, + Bun.env.APPWRITE_ESTIMATION_SESSION_COLLECTION_ID, + estimationId, + ); + } catch (e) { + error({ e }); + return res.json( + { + message: 'Estimation with this id does not exist', + }, + 400, + ); + } + + try { + const permissions: string[] = estimation['$permissions']; + permissions.push(`read("user:${userId}")`); + permissions.push(`update("user:${userId}")`); + + await databases.updateDocument( + Bun.env.APPWRITE_DATABASE_ID, + Bun.env.APPWRITE_ESTIMATION_SESSION_COLLECTION_ID, + estimationId, + {}, + permissions, + ); + + return res.json({ + message: 'Estimation session joined', + }); + } catch (e) { + error({ e }); + return res.json( + { + message: 'Failed to join estimation session', + }, + 500, + ); + } +}; + +const getSessionInfo = async ({ + client, + estimationId, + ctx: { log, res }, +}: { + client: Client; + estimationId: string; + ctx: AppwriteRuntimeContext; +}): Promise => { + const databases = new Databases(client); + + try { + const estimation = await databases.getDocument( + Bun.env.APPWRITE_DATABASE_ID, + Bun.env.APPWRITE_ESTIMATION_SESSION_COLLECTION_ID, + estimationId, + ); + + return res.json({ + result: { + id: estimation.$id, + name: estimation.name, + }, + }); + } catch (e) { + console.log({ e }); + log({ e }); + return res.json( + { + message: 'Estimation with this id does not exist', + }, + 400, + ); + } +}; + +export default async (ctx: AppwriteRuntimeContext) => { + const { req, res } = ctx; + const userId = req.headers['x-appwrite-user-id']; + if (!userId) { + return res.json( + { + message: 'Unauthorized', + }, + 401, + ); + } + + const action = req.query['action']; + const estimationId = req.query['estimationId']; + if (!action || !estimationId) { + return res.json( + { + message: 'Bad request', + }, + 400, + ); + } + + const client = new Client() + .setEndpoint(Bun.env.APPWRITE_FUNCTION_API_ENDPOINT) + .setProject(Bun.env.APPWRITE_FUNCTION_PROJECT_ID) + .setKey(req.headers['x-appwrite-key'] ?? ''); + + if (action === 'get-info') { + return await getSessionInfo({ + client, + ctx, + estimationId, + }); + } + + if (action === 'join') { + return await joinSession({ + client, + estimationId, + userId, + ctx, + }); + } + + return res.json( + { + message: 'Not found', + }, + 404, + ); +}; diff --git a/functions/EstimationSessionInvite/tsconfig.json b/functions/EstimationSessionInvite/tsconfig.json new file mode 100644 index 0000000..b4ecf7e --- /dev/null +++ b/functions/EstimationSessionInvite/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "types": ["bun-types"] + } +} diff --git a/src/lib/appwrite.ts b/src/lib/appwrite.ts index 78aa0c0..7e4c085 100644 --- a/src/lib/appwrite.ts +++ b/src/lib/appwrite.ts @@ -1,4 +1,4 @@ -import { Client, Account, Databases } from 'appwrite'; +import { Client, Account, Databases, Functions } from 'appwrite'; export const client = new Client(); @@ -10,7 +10,10 @@ export { ID } from 'appwrite'; export const account = new Account(client); export const databases = new Databases(client); +export const functions = new Functions(client); export const DATABASE_ID = import.meta.env.VITE_APPWRITE_DATABASE_ID; export const ESTIMATION_SESSION_COLLECTION_ID = import.meta.env .VITE_APPWRITE_ESTIMATION_SESSION_COLLECTION_ID; +export const SESSION_INVITE_FUNCTION_ID = import.meta.env + .VITE_SESSION_INVITE_FUNCTION_ID; diff --git a/src/lib/functions/estimationSessionInvite.ts b/src/lib/functions/estimationSessionInvite.ts new file mode 100644 index 0000000..0ad0bcc --- /dev/null +++ b/src/lib/functions/estimationSessionInvite.ts @@ -0,0 +1,88 @@ +import { ExecutionMethod } from 'appwrite'; +import { functions, SESSION_INVITE_FUNCTION_ID } from '../appwrite'; + +type SessionInviteInfo = + | { + success: true; + id: string; + name: string; + } + | { + success: false; + message: string; + }; + +type JoinSessionResponse = + | { + success: true; + } + | { + success: false; + message: string; + }; + +const getInviteInfo = async (sessionId: string): Promise => { + const result = await functions.createExecution( + SESSION_INVITE_FUNCTION_ID, + undefined, + false, + `/?action=get-info&estimationId=${sessionId}`, + ExecutionMethod.GET, + {}, + ); + + if (result.status === 'failed') { + return { + success: false, + message: result.errors ?? 'Failed to get estimation session info', + }; + } + + const responseBody = JSON.parse(result.responseBody); + if (responseBody.message) { + return { + success: false, + message: responseBody.message, + }; + } + + const { id, name } = responseBody.result; + return { + success: true, + id, + name, + }; +}; + +const joinSession = async (sessionId: string): Promise => { + const result = await functions.createExecution( + SESSION_INVITE_FUNCTION_ID, + undefined, + false, + `/?action=join&estimationId=${sessionId}`, + ExecutionMethod.GET, + {}, + ); + + if (result.status === 'failed') { + return { + success: false, + message: result.errors ?? 'Failed to join session', + }; + } + + const responseBody = JSON.parse(result.responseBody); + if (responseBody.message) { + return { + success: false, + message: responseBody.message, + }; + } + + return { + success: true, + }; +}; + +export { getInviteInfo, joinSession }; +export type { SessionInviteInfo }; diff --git a/src/main.tsx b/src/main.tsx index bd0e2fd..eaa50d4 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -17,6 +17,7 @@ import { EstimationContextProvider } from './lib/context/estimation'; import Estimation from './pages/Estimation/Estimation'; import Header from './components/Header'; import Profile from './pages/Profile'; +import Join from './pages/Join'; interface RouterContext { userContext: UserContextType; @@ -68,6 +69,12 @@ const profileRoute = createRoute({ getParentRoute: () => authenticatedRoute, }); +const joinRoute = createRoute({ + path: 'join/$sessionId', + component: Join, + getParentRoute: () => authenticatedRoute, +}); + const estimationSessionRoute = createRoute({ path: 'estimate/session/$sessionId', component: Estimation, @@ -77,6 +84,7 @@ const estimationSessionRoute = createRoute({ const router = createRouter({ routeTree: rootRoute.addChildren([ authenticatedRoute.addChildren([ + joinRoute, indexRoute, profileRoute, estimationSessionRoute, diff --git a/src/pages/Join.tsx b/src/pages/Join.tsx new file mode 100644 index 0000000..fa409ba --- /dev/null +++ b/src/pages/Join.tsx @@ -0,0 +1,79 @@ +import { useEffect, useState } from 'react'; +import { + getInviteInfo, + joinSession, + SessionInviteInfo, +} from '../lib/functions/estimationSessionInvite'; +import { getRouteApi } from '@tanstack/react-router'; + +const route = getRouteApi('/_authenticated/join/$sessionId'); + +const Join = () => { + const navigate = route.useNavigate(); + const { sessionId } = route.useParams(); + const [sessionInfo, setSessionInfo] = useState(); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + getInviteInfo(sessionId).then((sessionInfo) => { + setSessionInfo(sessionInfo); + setIsLoading(false); + }); + }, [sessionId]); + + const handleAccept = async () => { + setIsLoading(true); + await joinSession(sessionId); + + navigate({ + to: '/estimate/session/$sessionId', + params: { + sessionId: sessionId, + }, + }); + }; + + const handleReturnHome = () => { + navigate({ + to: '/', + }); + }; + + if (!sessionInfo || isLoading) { + // TODO: add loader + return

Loading...

; + } + + if (!sessionInfo.success) { + return

{sessionInfo.message}

; + } + + return ( +
+
+

+ You have been invited to join a new estimation session! +

+

+ Session Name: {sessionInfo.name} +

+
+ + +
+
+
+ ); +}; + +export default Join; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index b833d2b..ee1c3b5 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -5,6 +5,7 @@ interface ImportMetaEnv { readonly VITE_APPWRITE_PROJECT_ID: string; readonly VITE_APPWRITE_DATABASE_ID: string; readonly VITE_APPWRITE_ESTIMATION_SESSION_COLLECTION_ID: string; + readonly VITE_SESSION_INVITE_FUNCTION_ID: string; } interface ImportMeta {