commit 34c971222183275ea0f1678035482a38808e663f Author: ryoojiz Date: Wed Apr 3 10:15:51 2024 +0000 Initial commit Created from https://vercel.com/new diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..110a083 --- /dev/null +++ b/.env.example @@ -0,0 +1,47 @@ +NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY= +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= +NEXT_PUBLIC_FIREBASE_PROJECT_ID= + +NEXT_PUBLIC_ROOT_URL=http://localhost:3000 +NEXT_PUBLIC_SHA=development +NEXT_PUBLIC_BRANCH=local + +# Stripe Payments +NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= +NEXT_PUBLIC_STRIPE_PRICE_ID= +STRIPE_SECRET_KEY= +STRIPE_WEBHOOK_SECRET= + +MONGODB_URI="mongodb://localhost:27017" +MONGODB_DB="" + +# Discord Interactions API +DISCORD_CLIENT_ID="" +DISCORD_CLIENT_SECRET="" +DISCORD_APP_ID="" +DISCORD_TOKEN="" +DISCORD_PUBLIC_KEY="" +DISCORD_GUILD_ID="" + +# Roblox OAuth2 API +NEXT_PUBLIC_ROBLOX_CLIENT_ID="" +ROBLOX_CLIENT_SECRET="" + +# Mail +SENDGRID_API_KEY= + +# Firebase Authentication and Image Storage +FIREBASE_ADMIN_auth_provider_x509_cert_url= +FIREBASE_ADMIN_auth_uri= +FIREBASE_ADMIN_client_email= +FIREBASE_ADMIN_client_id= +FIREBASE_ADMIN_client_x509_cert_url= + +# (Encoded in base64) +FIREBASE_ADMIN_private_key= + +FIREBASE_ADMIN_private_key_id= +FIREBASE_ADMIN_project_id= +FIREBASE_ADMIN_token_uri= +FIREBASE_ADMIN_type= + diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..1ca6d4f --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "next/core-web-vitals", + "rules": { + "react/prop-types": 0 + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0869aa4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env.development +.env.production +.env.test + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..40d800b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,31 @@ +{ + "singleQuote": true, + "printWidth": 120, + "editor.formatOnSave": true, + "proseWrap": "always", + "tabWidth": 2, + "requireConfig": false, + "useTabs": false, + "trailingComma": "none", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "semi": true, + "singleAttributePerLine": true, + "importOrder": [ + "^react$", + "^@chakra-ui/react", + "^@chakra-ui/icons", + "^react-icons/(.*)$", + "", + "^@/lib/(.*)$", + "^@/contexts/(.*)$", + "^@/hooks/(.*)$", + "^@/util/(.*)$", + "^@/assets/(.*)$", + "^@/layouts/(.*)$", + "^@/components/(.*)$", + "^[./]" + ], + "importOrderSeparation": true, + "importOrderSortSpecifiers": true +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..44c9efc --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Pete Pongpeauk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c82f65 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# R&C XCS - Card Access Management Platform + +R&C XCS is a card access management platform that allows users to manage their card access to access points within experiences on the Roblox platform. + +### Features: +- Create and manage access points +- Organization and location-based access control +- Different allow-list parameters (e.g. card numbers, user ID, group ID assocation, etc.) +- Access groups +- Scan logs +- Member management +- Invitation links for organizations and registration +- ...and more! + +This project uses the following technologies: +- [React](https://reactjs.org/) +- [Next.js](https://nextjs.org/) +- [MongoDB](https://www.mongodb.com/) + +The backend is *serverless* and is bundled with Next.js in the `src/pages/api` directory. + +## Installation + +### Prerequisites + +- [Node.js](https://nodejs.org/en/) (v20.8.0 or higher) +- [Bun](https://bun.sh/) (1.0.7 or higher, recommended) or [npm](https://www.npmjs.com/) (9.8.0 or higher) +- [MongoDB Community Server](https://www.mongodb.com/) + +### Setup + +1. Clone the repository +2. Run `bun install` or `npm install` to install dependencies +3. Clone `.env.example` to `.env` and fill in the values +3. Run `bun dev` or `npm run dev` to start the development server + +## Contributing + +This project is open to contributions. I don't have a contributing guide yet, but feel free to open a pull request and I'll review it. + +## License + +This project is licensed under the MIT license. See [LICENSE.md](LICENSE.md) for more information. \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..68a93ff Binary files /dev/null and b/bun.lockb differ diff --git a/components.json b/components.json new file mode 100644 index 0000000..741b61a --- /dev/null +++ b/components.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tailwind": { + "config": "tailwind.config.js", + "css": "styles/globals.css", + "baseColor": "slate", + "cssVariables": false + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..1a1203e --- /dev/null +++ b/next.config.js @@ -0,0 +1,103 @@ +/** @type {import('next').NextConfig} */ + +const withBundleAnalyzer = require('@next/bundle-analyzer')({ + enabled: process.env.ANALYZE === 'true' +}) + +// const withPWA = require('next-pwa')({ +// dest: 'public', +// register: true, +// skipWaiting: true, +// disable: true, +// }); + +module.exports = withBundleAnalyzer({ + reactStrictMode: true, + async rewrites() { + return [ + // legacy oauth endpoints + { + source: '/platform/verify/:slug*', + destination: '/verify/:slug*' + }, + { + source: '/@:username', + destination: '/users/:username' + }, + { + source: '/api/v1/roblox/users/:slug*', + destination: 'https://users.roblox.com/:slug*' + }, + { + source: '/api/v1/roblox/groups/:slug*', + destination: 'https://groups.roblox.com/:slug*' + }, + { + source: '/api/v1/roblox/games/:slug*', + destination: 'https://games.roblox.com/:slug*' + }, + { + source: '/api/v1/roblox/thumbnails/:slug*', + destination: 'https://thumbnails.roblox.com/:slug*' + }, + { + source: '/api/v1/ip-api/:slug*', + destination: 'http://ip-api.com/:slug*' + } + ]; + }, + async redirects() { + return [ + { + source: '/login', + destination: '/auth/login', + permanent: true + }, + { + source: '/signup', + destination: '/auth/signup', + permanent: true + }, + { + source: '/logout', + destination: '/auth/logout', + permanent: true + }, + { + source: '/platform', + destination: '/platform/home', + permanent: true + }, + { + source: '/platform/:slug*', + destination: '/:slug*', + permanent: false + }, + { + source: '/users/:slug', + destination: '/@:slug', + permanent: false + }, + { + source: '/user/:slug', + destination: '/@:slug', + permanent: false + }, + { + source: '/invite/:slug*', + destination: '/invitation/:slug*', + permanent: false + }, + { + source: '/AxesysAPI/:slug*', + destination: '/api/v1/axesys/:slug*', + permanent: false + }, + { + source: '/api/v1/AxesysAPI/:slug*', + destination: '/api/v1/axesys/:slug*', + permanent: false + } + ]; + } +}); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..4e9d604 --- /dev/null +++ b/package.json @@ -0,0 +1,79 @@ +{ + "name": "xcs4", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "analyze": "cross-env ANALYZE=true next build", + "analyze:server": "cross-env BUNDLE_ANALYZE=server next build", + "analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build" + }, + "dependencies": { + "@chakra-ui/icons": "^2.0.19", + "@chakra-ui/next-js": "^2.1.5", + "@chakra-ui/react": "^2.8.0", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@monaco-editor/react": "^4.5.1", + "@mui/material": "^5.13.7", + "@mui/x-data-grid": "^6.9.2", + "@sendgrid/mail": "^7.7.0", + "@tanstack/react-query": "^4.32.6", + "@tanstack/react-table": "^8.9.3", + "@trivago/prettier-plugin-sort-imports": "^4.2.0", + "@types/gm": "^1.25.1", + "@types/node": "^20.5.0", + "@types/randomstring": "^1.1.8", + "@types/react": "18.2.14", + "@types/react-dom": "18.2.6", + "@types/uuid": "^9.0.2", + "autoprefixer": "10.4.14", + "chakra-react-select": "^4.7.0", + "class-variance-authority": "^0.6.1", + "clsx": "^1.2.1", + "discord-api-types": "^0.37.50", + "eslint": "8.44.0", + "eslint-config-next": "13.4.7", + "firebase": "^9.23.0", + "firebase-admin": "^11.9.0", + "formik": "^2.4.2", + "framer-motion": "^10.13.0", + "generate-api-key": "^1.0.2", + "gray-matter": "^4.0.3", + "lru-cache": "7.12.0", + "mergician": "^1.1.0", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "mongodb": "^5.6.0", + "next": "13.4.7", + "next-pwa": "^5.6.0", + "nextjs-progressbar": "^0.0.16", + "postcss": "8.4.24", + "prettier": "^3.0.0", + "randomstring": "^1.3.0", + "react": "18.2.0", + "react-data-grid": "^7.0.0-beta.34", + "react-dom": "18.2.0", + "react-fast-marquee": "^1.6.0", + "react-firebase": "^2.2.8", + "react-firebase-hooks": "^5.1.1", + "react-icons": "^4.10.1", + "react-markdown": "^9.0.0", + "remark": "^15.0.1", + "remark-html": "^16.0.1", + "request-ip": "^3.3.0", + "sharp": "^0.32.6", + "tailwind-merge": "^1.13.2", + "tailwindcss": "3.3.2", + "tailwindcss-animate": "^1.0.6", + "tweetnacl": "^1.0.3", + "typescript": "5.1.6" + }, + "devDependencies": { + "@next/bundle-analyzer": "^13.4.13", + "cross-env": "^7.0.3" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..5cbc2c7 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +}; diff --git a/posts/platform-updates-2023-08-13.md b/posts/platform-updates-2023-08-13.md new file mode 100644 index 0000000..f43804f --- /dev/null +++ b/posts/platform-updates-2023-08-13.md @@ -0,0 +1,81 @@ +--- +title: 'Platform Updates - 2023-08-13' +date: '2023-08-13' + +thumbnail: '/images/blog/launch/1.png' +thumbnailAlt: 'R&C XCS' + +author: 'restrafes' +authorImage: '/images/authors/restrafes.png' + +category: 'Updates' +--- + +Hello, and welcome to the first ever blog post on the platform! For our first post, we'll be going over some of the +updates that have been made to the platform since our beta launch. + +Let's get started! + +## Adding members just got an upgrade! + +We've updated the member add flow to be more intuitive and easier to use. The new flow is shown below: + +![Member flow](/images/blog/updates-2023-08-13/1.jpeg) + +## 🔥🔥 Card member types 🔥🔥 + +In an ongoing effort to make XCS a full replacement to traditional access point readers, we've added a new member type: +card numbers! You can now specify what card numbers are accepted for each access point. Ranges are supported, so you +don't need to add each card number individually (e.g. 1-24.) + +## In-app invitations have arrived! + +We've added an in-app invitation system for organizations. This allows you to invite members to your organization +without having to share a link. Of course, you can still share a link if you want to. + +![Organization invitations screen](/images/blog/updates-2023-08-13/2.jpeg) + +## Access group priority levels + +You can now specify a priority level for each access group. This allows you to specify which access groups' scan data +take precedence over others. + +For example, if you have an access group with a priority level of 1 with the following scan data: + + { + "isRestricted": true, + "allowedFloors": [1, 3] + } + +-and another access group with a priority level of 2 with the following scan data: + + { + "isRestricted": false, + "allowedFloors": [1, 4, 5] + } + +-the following scan data will be returned for a member with both access groups: + + { + "isRestricted": false, + "allowedFloors": [1, 3, 4, 5] + } + +## Beta testers now get a sweet new badge! 🎉 + +We've added a new badge for beta testers! If you registered your XCS account before the platform was launched to the +public, you should have the badge already. + +![Beta tester badge](/images/blog/updates-2023-08-13/3.jpeg) + +## Other features & changes + +- You are now able to upload custom icons to your organization. +- Organizations now get a public page. + +That wraps up this blog post! We hope you enjoy the features as much as we enjoyed making them. If you have any feedback +or suggestions, please let us know in our [official Discord server!](https://discord.gg/BWVa3yE9M3) + +Until next time, + +restrafes diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000..c6f0022 Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000..03eee62 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..ca20124 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..cf5c82a Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..0800119 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon-alt.ico b/public/favicon-alt.ico new file mode 100644 index 0000000..cfc9309 Binary files /dev/null and b/public/favicon-alt.ico differ diff --git a/public/favicon-alt2.ico b/public/favicon-alt2.ico new file mode 100644 index 0000000..b9c467b Binary files /dev/null and b/public/favicon-alt2.ico differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..109a495 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/images/7534-dababy.png b/public/images/7534-dababy.png new file mode 100644 index 0000000..61772c3 Binary files /dev/null and b/public/images/7534-dababy.png differ diff --git a/public/images/achievements/early-access.png b/public/images/achievements/early-access.png new file mode 100644 index 0000000..7025140 Binary files /dev/null and b/public/images/achievements/early-access.png differ diff --git a/public/images/authors/restrafes.png b/public/images/authors/restrafes.png new file mode 100644 index 0000000..9e65f14 Binary files /dev/null and b/public/images/authors/restrafes.png differ diff --git a/public/images/blog/launch/1.png b/public/images/blog/launch/1.png new file mode 100644 index 0000000..c5e11b1 Binary files /dev/null and b/public/images/blog/launch/1.png differ diff --git a/public/images/blog/updates-2023-08-13/1.jpeg b/public/images/blog/updates-2023-08-13/1.jpeg new file mode 100644 index 0000000..a4e723b Binary files /dev/null and b/public/images/blog/updates-2023-08-13/1.jpeg differ diff --git a/public/images/blog/updates-2023-08-13/2.jpeg b/public/images/blog/updates-2023-08-13/2.jpeg new file mode 100644 index 0000000..365a89a Binary files /dev/null and b/public/images/blog/updates-2023-08-13/2.jpeg differ diff --git a/public/images/blog/updates-2023-08-13/3.jpeg b/public/images/blog/updates-2023-08-13/3.jpeg new file mode 100644 index 0000000..630c9e4 Binary files /dev/null and b/public/images/blog/updates-2023-08-13/3.jpeg differ diff --git a/public/images/default-avatar-card.png b/public/images/default-avatar-card.png new file mode 100644 index 0000000..aa23864 Binary files /dev/null and b/public/images/default-avatar-card.png differ diff --git a/public/images/default-avatar-location.png b/public/images/default-avatar-location.png new file mode 100644 index 0000000..6f9ea81 Binary files /dev/null and b/public/images/default-avatar-location.png differ diff --git a/public/images/default-avatar-organization.png b/public/images/default-avatar-organization.png new file mode 100644 index 0000000..5c06758 Binary files /dev/null and b/public/images/default-avatar-organization.png differ diff --git a/public/images/default-avatar.png b/public/images/default-avatar.png new file mode 100644 index 0000000..9e65f14 Binary files /dev/null and b/public/images/default-avatar.png differ diff --git a/public/images/hero1.jpg b/public/images/hero1.jpg new file mode 100644 index 0000000..040f784 Binary files /dev/null and b/public/images/hero1.jpg differ diff --git a/public/images/hero2.jpeg b/public/images/hero2.jpeg new file mode 100644 index 0000000..0b0a229 Binary files /dev/null and b/public/images/hero2.jpeg differ diff --git a/public/images/hero3.jpg b/public/images/hero3.jpg new file mode 100644 index 0000000..58c1e0d Binary files /dev/null and b/public/images/hero3.jpg differ diff --git a/public/images/hero4.jpeg b/public/images/hero4.jpeg new file mode 100644 index 0000000..4d44bcc Binary files /dev/null and b/public/images/hero4.jpeg differ diff --git a/public/images/hero5.jpeg b/public/images/hero5.jpeg new file mode 100644 index 0000000..aaa7cca Binary files /dev/null and b/public/images/hero5.jpeg differ diff --git a/public/images/hero6.png b/public/images/hero6.png new file mode 100644 index 0000000..ba54746 Binary files /dev/null and b/public/images/hero6.png differ diff --git a/public/images/home-hero.jpg b/public/images/home-hero.jpg new file mode 100644 index 0000000..394074a Binary files /dev/null and b/public/images/home-hero.jpg differ diff --git a/public/images/icons/icon-128x128.png b/public/images/icons/icon-128x128.png new file mode 100644 index 0000000..ba35628 Binary files /dev/null and b/public/images/icons/icon-128x128.png differ diff --git a/public/images/icons/icon-144x144.png b/public/images/icons/icon-144x144.png new file mode 100644 index 0000000..c5e8ae7 Binary files /dev/null and b/public/images/icons/icon-144x144.png differ diff --git a/public/images/icons/icon-152x152.png b/public/images/icons/icon-152x152.png new file mode 100644 index 0000000..5917d5e Binary files /dev/null and b/public/images/icons/icon-152x152.png differ diff --git a/public/images/icons/icon-192x192.png b/public/images/icons/icon-192x192.png new file mode 100644 index 0000000..7e7d44f Binary files /dev/null and b/public/images/icons/icon-192x192.png differ diff --git a/public/images/icons/icon-384x384.png b/public/images/icons/icon-384x384.png new file mode 100644 index 0000000..c6f35ea Binary files /dev/null and b/public/images/icons/icon-384x384.png differ diff --git a/public/images/icons/icon-512x512.png b/public/images/icons/icon-512x512.png new file mode 100644 index 0000000..700329c Binary files /dev/null and b/public/images/icons/icon-512x512.png differ diff --git a/public/images/icons/icon-72x72.png b/public/images/icons/icon-72x72.png new file mode 100644 index 0000000..38338ec Binary files /dev/null and b/public/images/icons/icon-72x72.png differ diff --git a/public/images/icons/icon-96x96.png b/public/images/icons/icon-96x96.png new file mode 100644 index 0000000..30b6500 Binary files /dev/null and b/public/images/icons/icon-96x96.png differ diff --git a/public/images/icons/icon-apple.png b/public/images/icons/icon-apple.png new file mode 100644 index 0000000..d5b579a Binary files /dev/null and b/public/images/icons/icon-apple.png differ diff --git a/public/images/login1.jpeg b/public/images/login1.jpeg new file mode 100644 index 0000000..fd49338 Binary files /dev/null and b/public/images/login1.jpeg differ diff --git a/public/images/login2.jpeg b/public/images/login2.jpeg new file mode 100644 index 0000000..4a69d94 Binary files /dev/null and b/public/images/login2.jpeg differ diff --git a/public/images/login4.jpeg b/public/images/login4.jpeg new file mode 100644 index 0000000..e8db78b Binary files /dev/null and b/public/images/login4.jpeg differ diff --git a/public/images/logo-black.png b/public/images/logo-black.png new file mode 100644 index 0000000..362a83c Binary files /dev/null and b/public/images/logo-black.png differ diff --git a/public/images/logo-black.png.old.png b/public/images/logo-black.png.old.png new file mode 100644 index 0000000..672e263 Binary files /dev/null and b/public/images/logo-black.png.old.png differ diff --git a/public/images/logo-black2.png b/public/images/logo-black2.png new file mode 100644 index 0000000..da69878 Binary files /dev/null and b/public/images/logo-black2.png differ diff --git a/public/images/logo-square.jpeg b/public/images/logo-square.jpeg new file mode 100644 index 0000000..3819a8e Binary files /dev/null and b/public/images/logo-square.jpeg differ diff --git a/public/images/logo-square.jpg b/public/images/logo-square.jpg new file mode 100644 index 0000000..7066eaf Binary files /dev/null and b/public/images/logo-square.jpg differ diff --git a/public/images/logo-square.png b/public/images/logo-square.png new file mode 100644 index 0000000..a98a9cd Binary files /dev/null and b/public/images/logo-square.png differ diff --git a/public/images/logo-white.png b/public/images/logo-white.png new file mode 100644 index 0000000..b2f24de Binary files /dev/null and b/public/images/logo-white.png differ diff --git a/public/images/logo-white.png.old.png b/public/images/logo-white.png.old.png new file mode 100644 index 0000000..92509f0 Binary files /dev/null and b/public/images/logo-white.png.old.png differ diff --git a/public/images/logo-white2.png b/public/images/logo-white2.png new file mode 100644 index 0000000..f1a5d90 Binary files /dev/null and b/public/images/logo-white2.png differ diff --git a/public/images/logo.jpg b/public/images/logo.jpg new file mode 100644 index 0000000..133dbf4 Binary files /dev/null and b/public/images/logo.jpg differ diff --git a/public/images/splashscreens/ipad_splash.png b/public/images/splashscreens/ipad_splash.png new file mode 100644 index 0000000..0e13c12 Binary files /dev/null and b/public/images/splashscreens/ipad_splash.png differ diff --git a/public/images/splashscreens/ipadpro1_splash.png b/public/images/splashscreens/ipadpro1_splash.png new file mode 100644 index 0000000..71ea534 Binary files /dev/null and b/public/images/splashscreens/ipadpro1_splash.png differ diff --git a/public/images/splashscreens/ipadpro2_splash.png b/public/images/splashscreens/ipadpro2_splash.png new file mode 100644 index 0000000..f5a94f6 Binary files /dev/null and b/public/images/splashscreens/ipadpro2_splash.png differ diff --git a/public/images/splashscreens/ipadpro3_splash.png b/public/images/splashscreens/ipadpro3_splash.png new file mode 100644 index 0000000..0c4535b Binary files /dev/null and b/public/images/splashscreens/ipadpro3_splash.png differ diff --git a/public/images/splashscreens/iphone5_splash.png b/public/images/splashscreens/iphone5_splash.png new file mode 100644 index 0000000..b60d1a6 Binary files /dev/null and b/public/images/splashscreens/iphone5_splash.png differ diff --git a/public/images/splashscreens/iphone6_splash.png b/public/images/splashscreens/iphone6_splash.png new file mode 100644 index 0000000..2ed4211 Binary files /dev/null and b/public/images/splashscreens/iphone6_splash.png differ diff --git a/public/images/splashscreens/iphoneplus_splash.png b/public/images/splashscreens/iphoneplus_splash.png new file mode 100644 index 0000000..ed53b55 Binary files /dev/null and b/public/images/splashscreens/iphoneplus_splash.png differ diff --git a/public/images/splashscreens/iphonex_splash.png b/public/images/splashscreens/iphonex_splash.png new file mode 100644 index 0000000..255e989 Binary files /dev/null and b/public/images/splashscreens/iphonex_splash.png differ diff --git a/public/images/splashscreens/iphonexr_splash.png b/public/images/splashscreens/iphonexr_splash.png new file mode 100644 index 0000000..2665d0c Binary files /dev/null and b/public/images/splashscreens/iphonexr_splash.png differ diff --git a/public/images/splashscreens/iphonexsmax_splash.png b/public/images/splashscreens/iphonexsmax_splash.png new file mode 100644 index 0000000..a698577 Binary files /dev/null and b/public/images/splashscreens/iphonexsmax_splash.png differ diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..ac3a5fb --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,52 @@ +{ + "name": "R&C XCS", + "short_name": "XCS", + "theme_color": "#fff", + "background_color": "#000", + "display": "standalone", + "orientation": "", + "scope": "/", + "start_url": "/auth/login", + "icons": [ + { + "src": "images/icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "images/icons/icon-96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "images/icons/icon-128x128.png", + "sizes": "128x128", + "type": "image/png" + }, + { + "src": "images/icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "images/icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "images/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "images/icons/icon-384x384.png", + "sizes": "384x384", + "type": "image/png" + }, + { + "src": "images/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} \ No newline at end of file diff --git a/public/next.svg b/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg new file mode 100644 index 0000000..d2f8422 --- /dev/null +++ b/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/blacklist.ts b/src/blacklist.ts new file mode 100644 index 0000000..1179124 --- /dev/null +++ b/src/blacklist.ts @@ -0,0 +1 @@ +export const blacklist = {}; diff --git a/src/components/AccessGroupEditModal.tsx b/src/components/AccessGroupEditModal.tsx new file mode 100644 index 0000000..4a4d8b4 --- /dev/null +++ b/src/components/AccessGroupEditModal.tsx @@ -0,0 +1,652 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { + Badge, + Box, + Button, + Flex, + FormControl, + FormHelperText, + FormLabel, + IconButton, + Input, + InputGroup, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + NumberDecrementStepper, + NumberIncrementStepper, + NumberInput, + NumberInputField, + NumberInputStepper, + Portal, + Skeleton, + SkeletonText, + Spacer, + Stack, + Switch, + Table, TableCaption, TableContainer, Tbody, Td, Text, + Th, Thead, + Tr, + VStack, + chakra, + useColorModeValue, + useDisclosure, + useToast +} from '@chakra-ui/react'; + + +import { IoIosCreate, IoIosRemoveCircle } from 'react-icons/io'; +import { IoSave } from 'react-icons/io5'; +import { MdEditSquare } from 'react-icons/md'; + +import Editor from '@monaco-editor/react'; +import { Field, Form, Formik } from 'formik'; + + +import { useAuthContext } from '@/contexts/AuthContext'; + +import DeleteDialog from '@/components/DeleteDialog'; + +import CreateAccessGroupDialog from './CreateAccessGroupDialog'; + +const ChakraEditor = chakra(Editor); + +export default function AccessGroupEditModal({ + isOpen, + onOpen, + onClose, + onRefresh, + clientMember, + groups, + organization, + location, + onGroupRemove +}: any) { + const { user } = useAuthContext(); + const toast = useToast(); + const [focusedGroup, setFocusedGroup] = useState(null); + const themeBorderColor = useColorModeValue('gray.200', 'gray.700'); + + const groupSearchRef = useRef(null); + const [filteredGroups, setFilteredGroups] = useState([]); + + const editButtonsRef = useRef(null); + + const { + isOpen: deleteGroupDialogOpen, + onOpen: deleteGroupDialogOnOpen, + onClose: deleteGroupDialogOnClose + } = useDisclosure(); + + const { isOpen: createModalOpen, onOpen: createModalOnOpen, onClose: createModalOnClose } = useDisclosure(); + + const filterGroups = useCallback((query: string) => { + if (!query) return groups; + return Object.keys(groups) + .filter((group: any) => groups[group].name.toLowerCase().includes(query.toLowerCase())) + .map((group: any) => groups[group]); + }, [groups]); + + useEffect(() => { + setFilteredGroups(groups || {}); + }, [groups]); + + useEffect(() => { + if (!organization) return; + setFilteredGroups(filterGroups(groupSearchRef?.current?.value)); + if (focusedGroup) { + setFocusedGroup(Object.values(groups as any).find((group: any) => group.id === focusedGroup.id)); + } + }, [organization]); + + return ( + <> + { + deleteGroupDialogOnClose(); + onGroupRemove(focusedGroup); + setFocusedGroup(null); + }} + /> + { + onRefresh(); + createModalOnClose(); + }} + /> + + + + Manage Access Groups + + + + + + Search Access Group + { + if (e.target?.value) { + setFilteredGroups(filterGroups(e.target?.value)); + } else { + setFilteredGroups(groups); + } + }} + /> + + + + + + + + + + + + + + + {organization ? ( + Object.keys(filteredGroups).map((group: any) => ( + + + + + )) + ) : ( + <> + {Array.from(Array(8).keys()).map((i) => ( + + + + + ))} + + )} + + {(!Object.keys(groups || {})?.length || !Object.keys(filteredGroups || {})?.length) && ( + No access groups found. + )} +
NameActions
+ + + {filteredGroups[group].name} + {filteredGroups[group].config?.openToEveryone && ( + + Everyone + + )} + + {filteredGroups[group].config?.active ? 'Active' : 'Inactive'} + + + {filteredGroups[group].description && ( + + {filteredGroups[group].description} + + )} + + + + } + onClick={() => { + setFocusedGroup(filteredGroups[group]); + deleteGroupDialogOnOpen(); + }} + /> +
+ + + +
+
+ {/* Edit Group */} + + + {!focusedGroup || !organization ? ( + + Select an access group to manage. + + ) : ( + + {/* Header */} + + + + + {focusedGroup?.name} + + + + + {/* Body */} + { + user.getIdToken().then((token: string) => { + fetch(`/api/v1/organizations/${organization?.id}/access-groups/${focusedGroup?.id}`, { + method: 'PATCH', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + name: values?.name, + locationId: location?.id, + description: values?.description, + scanData: values?.scanData, + priority: values?.priority, + config: { + active: values?.configActive, + openToEveryone: values?.configOpenToEveryone + } + }) + }) + .then((res) => { + if (res.status === 200) { + return res.json(); + } else { + return res.json().then((json) => { + throw new Error(json.message); + }); + } + }) + .then((data) => { + toast({ + title: data.message, + status: 'success', + duration: 5000, + isClosable: true + }); + onRefresh(); + }) + .catch((error) => { + toast({ + title: 'There was an error updating the access group.', + description: error.message, + status: 'error', + duration: 5000, + isClosable: true + }); + }) + .finally(() => { + actions.setSubmitting(false); + }); + }); + }} + > + {(props) => ( +
+ + + + + {({ field, form }: any) => ( + + Name + + + )} + + + {({ field, form }: any) => ( + + Short Description + + + )} + + + {({ field, form }: any) => ( + + Priority + + { + form.setFieldValue('priority', value); + }} + > + + + + + + + + + )} + + + + + {({ field, form }: any) => ( + + Active + + { + form.setFieldValue('configActive', e.target.checked); + }} + /> + + Whether or not this access group is active. + + )} + + + {({ field, form }: any) => ( + + Everyone + + { + form.setFieldValue('configOpenToEveryone', e.target.checked); + }} + /> + + + Whether or not this access group is open to everyone whose access is granted. + + + )} + + + + {({ field, form }: any) => ( + + Scan Data + + + { + form.setFieldValue('scanData', value); + }} + /> + + + + This is the data that will be returned when a user under this access group + scans their card. (User scan data takes priority over access group scan data + when it is merged). + + + )} + + + + + + + + + +
+ )} +
+
+ )} +
+
+
+
+
+ + + + + + +
+
+ + ); +} diff --git a/src/components/CheckActivationCodeModal.tsx b/src/components/CheckActivationCodeModal.tsx new file mode 100644 index 0000000..355a94e --- /dev/null +++ b/src/components/CheckActivationCodeModal.tsx @@ -0,0 +1,136 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { useRef } from 'react'; + +import { + Button, + FormControl, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, + VStack, + useColorModeValue, + useToast +} from '@chakra-ui/react'; + +import { useAuthContext } from '@/contexts/AuthContext'; +import { Field, Form, Formik } from 'formik'; +import { useRouter } from 'next/router'; + +export default function CheckActivationCodeModal({ + isOpen, + onClose, +}: { + isOpen: boolean; + onClose: () => void; +}) { + const toast = useToast(); + const initialRef = useRef(null); + const { user } = useAuthContext(); + const { push } = useRouter(); + + return ( + <> + { + const reformatCode = values.code.replace(`${process.env.NEXT_PUBLIC_ROOT_URL}/invitation/`, ''); + await actions.setValues({ 'code': reformatCode }); + await fetch(`/api/v1/activation/${encodeURIComponent(reformatCode || 0)}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }) + .then((res) => { + if (res.status === 200) { + return res.json(); + } else { + return res.json().then((json) => { + throw new Error(json.message); + }); + } + }) + .then((data) => { + onClose(); + push(`/auth/activate/${reformatCode}`) + }) + .catch((error) => { + toast({ + title: 'Error', + description: error.message, + status: 'error', + duration: 5000, + isClosable: true + }); + }) + .finally(() => { + actions.setSubmitting(false); + }); + }} + > + {(props) => ( + + +
+ + Activation Code + + + + + {({ field, form }: any) => ( + + Activation Code + + + )} + + + + Have an activation code? Enter it here to create an account. + + + + + + + + +
+
+ )} +
+ + ); +} diff --git a/src/components/CreateAccessGroupDialog.tsx b/src/components/CreateAccessGroupDialog.tsx new file mode 100644 index 0000000..57ef826 --- /dev/null +++ b/src/components/CreateAccessGroupDialog.tsx @@ -0,0 +1,164 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { useRef } from 'react'; + +import { + Button, + FormControl, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, + Textarea, + VStack, + useColorModeValue, + useToast +} from '@chakra-ui/react'; + +import { Field, Form, Formik } from 'formik'; + +import { useAuthContext } from '@/contexts/AuthContext'; + +export default function CreateAccessGroupDialog({ + isOpen, + onClose, + organization, + location, + onCreate +}: { + isOpen: boolean; + onClose: () => void; + organization: any; + location?: any; + onCreate: (location: any) => void; +}) { + const toast = useToast(); + const initialRef = useRef(null); + const finalRef = useRef(null); + const { user } = useAuthContext(); + + return ( + <> + { + user.getIdToken().then((token: any) => { + fetch(`/api/v1/organizations/${organization.id}/access-groups`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + name: values.name, + locationId: location?.id, + description: values.description, + scanData: {} + }) + }) + .then((res) => { + if (res.status === 200) { + return res.json(); + } else { + return res.json().then((json) => { + throw new Error(json.message); + }); + } + }) + .then((data) => { + toast({ + title: data.message, + status: 'success', + duration: 5000, + isClosable: true + }); + actions.resetForm(); + onClose(); + onCreate(data.id); + }) + .catch((error) => { + toast({ + title: 'There was an error creating the access group.', + description: error.message, + status: 'error', + duration: 5000, + isClosable: true + }); + }) + .finally(() => { + actions.setSubmitting(false); + }); + }); + }} + > + {(props) => ( + + +
+ + New Access Group + + + + + {({ field, form }: any) => ( + + Name + + + )} + + + {({ field, form }: any) => ( + + Short Description +