Implement session invites
This commit is contained in:
parent
9b45d372fa
commit
1463c05731
1
.env
1
.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
|
||||
|
|
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
|
@ -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
|
Binary file not shown.
|
@ -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 {};
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
|
@ -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<AppwriteSendReturn> => {
|
||||
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<AppwriteSendReturn> => {
|
||||
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,
|
||||
);
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["bun-types"]
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<SessionInviteInfo> => {
|
||||
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<JoinSessionResponse> => {
|
||||
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 };
|
|
@ -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,
|
||||
|
|
|
@ -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<SessionInviteInfo>();
|
||||
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 <p>Loading...</p>;
|
||||
}
|
||||
|
||||
if (!sessionInfo.success) {
|
||||
return <p>{sessionInfo.message}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center bg-gray-50 transition-colors dark:bg-nero-900">
|
||||
<div className="max-w-lg rounded-lg bg-white p-8 text-center shadow-lg dark:bg-nero-800">
|
||||
<h1 className="mb-4 text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
You have been invited to join a new estimation session!
|
||||
</h1>
|
||||
<p className="mb-6 text-lg text-gray-700 dark:text-gray-300">
|
||||
Session Name: <strong>{sessionInfo.name}</strong>
|
||||
</p>
|
||||
<div className="flex justify-center gap-4">
|
||||
<button
|
||||
onClick={handleAccept}
|
||||
className="rounded-md bg-indigo-600 px-6 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-indigo-500"
|
||||
>
|
||||
Accept
|
||||
</button>
|
||||
<button
|
||||
onClick={handleReturnHome}
|
||||
className="rounded-md bg-gray-300 px-6 py-2 text-sm font-semibold text-gray-900 shadow-sm transition-colors hover:bg-gray-200 dark:bg-nero-700 dark:text-gray-100 dark:hover:bg-gray-600"
|
||||
>
|
||||
Return to Home
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Join;
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue