├── .env.example ├── .eslintrc.js ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .prettierrc.js ├── README.md ├── cache.config.js ├── jsconfig.json ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── .well-known │ └── assetlinks.json ├── favicon.ico ├── fonts │ └── Verveine │ │ ├── Verveine.eot │ │ ├── Verveine.svg │ │ ├── Verveine.ttf │ │ ├── Verveine.woff │ │ └── Verveine.woff2 ├── img │ ├── articles │ │ ├── progress-update-1 │ │ │ └── card.jpg │ │ └── progress-update-2 │ │ │ └── card.jpg │ ├── avatar.jpg │ ├── bg-dark.png │ ├── card.jpg │ └── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest └── robots.txt ├── src ├── articles │ ├── progress-update-1.mdx │ └── progress-update-2.mdx ├── components │ ├── App │ │ ├── Alert.js │ │ ├── AlertManager.js │ │ ├── Avatar.js │ │ ├── ClientOnly.js │ │ ├── Compose.js │ │ ├── Icon.js │ │ ├── ImageGrid.js │ │ ├── LoadLink.js │ │ ├── LoadingButton.js │ │ ├── Modal.js │ │ ├── Navigation │ │ │ ├── BottomNav.js │ │ │ ├── SideNav.js │ │ │ └── TopNav.js │ │ ├── Notification.js │ │ ├── PageLayout.js │ │ ├── Post.js │ │ ├── Skeleton.js │ │ └── Tabs.js │ ├── Global │ │ ├── BaseLayout.js │ │ ├── Head.js │ │ ├── Logo.js │ │ ├── ThemeManager.js │ │ └── Transition.js │ └── Marketing │ │ ├── ConfirmationModal.js │ │ ├── EarlyAccessForm.js │ │ └── Video.js ├── context │ ├── alerts.js │ └── theme.js ├── hooks │ ├── alert.js │ ├── click-outside.js │ ├── format.js │ ├── meta.js │ ├── notifications.js │ ├── sticky.js │ ├── tailwind.js │ ├── theme.js │ └── user.js ├── middleware │ └── auth.js ├── pages │ ├── 404.js │ ├── [profile] │ │ ├── index.js │ │ ├── posts │ │ │ └── [post].js │ │ └── replies.js │ ├── _app.js │ ├── _document.js │ ├── _error.js │ ├── api │ │ ├── auth │ │ │ ├── login.js │ │ │ └── register.js │ │ └── meta │ │ │ ├── post.js │ │ │ └── profile.js │ ├── blog │ │ └── [slug].js │ ├── code-of-conduct.mdx │ ├── home.js │ ├── index.js │ ├── login.js │ ├── meta │ │ ├── post.js │ │ └── profile.js │ ├── notifications.js │ ├── onboarding │ │ ├── identity.js │ │ ├── profile.js │ │ ├── subscription.js │ │ └── verify.js │ ├── register.js │ ├── search.js │ └── settings.js ├── scss │ └── app.scss └── utils │ ├── Client.js │ ├── arr.js │ ├── auth.js │ ├── constants.js │ ├── encoding.js │ ├── errors.js │ ├── puppeteer.js │ └── redirectTo.js ├── tailwind.config.js └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | AURALITE_ID= 2 | AURALITE_SECRET= 3 | NEXT_PUBLIC_AURALITE_URL= 4 | NEXT_PUBLIC_STRIPE_KEY= 5 | VERCEL_REGION=dev1 6 | VERCEL_URL="http://localhost:3000" 7 | 8 | NODE_TLS_REJECT_UNAUTHORIZED=0 -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | env: { 5 | node: true, 6 | browser: true, 7 | es6: true, 8 | commonjs: true, 9 | }, 10 | extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:jsx-a11y/recommended', 'plugin:prettier/recommended'], 11 | parserOptions: { 12 | ecmaFeatures: { 13 | jsx: true, 14 | }, 15 | ecmaVersion: 2020, 16 | }, 17 | plugins: ['react'], 18 | rules: { 19 | 'import/prefer-default-export': 0, 20 | 'no-console': 'warn', 21 | 'no-nested-ternary': 0, 22 | 'no-underscore-dangle': 0, 23 | 'no-unused-expressions': ['error', { allowTernary: true }], 24 | camelcase: 0, 25 | 'react/self-closing-comp': 1, 26 | 'react/jsx-filename-extension': [1, { extensions: ['.js', 'jsx'] }], 27 | 'react/prop-types': 0, 28 | 'react/destructuring-assignment': 0, 29 | 'react/jsx-no-comment-textnodes': 0, 30 | 'react/jsx-props-no-spreading': 0, 31 | 'react/no-array-index-key': 0, 32 | 'react/no-unescaped-entities': 0, 33 | 'react/require-default-props': 0, 34 | 'jsx-a11y/label-has-for': 0, 35 | 'jsx-a11y/anchor-is-valid': 0, 36 | 'react/react-in-jsx-scope': 0, 37 | 'linebreak-style': ['error', 'unix'], 38 | semi: ['error', 'never'], 39 | 'prettier/prettier': ['error', {}, { usePrettierrc: true }], 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | schedule: 9 | - cron: '0 8 * * 2' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | language: ['javascript'] 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v2 24 | with: 25 | fetch-depth: 2 26 | 27 | - run: git checkout HEAD^2 28 | if: ${{ github.event_name == 'pull_request' }} 29 | 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@v1 32 | with: 33 | languages: ${{ matrix.language }} 34 | 35 | - name: Perform CodeQL Analysis 36 | uses: github/codeql-action/analyze@v1 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # editors 33 | .vscode 34 | .vercel -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | printWidth: 1000, 5 | tabWidth: 4, 6 | trailingComma: "es5", 7 | useTabs: true, 8 | bracketSpacing: true, 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # (WIP) Auralite Web -------------------------------------------------------------------------------- /cache.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = [ 4 | { 5 | urlPattern: '/', 6 | handler: 'NetworkFirst', 7 | options: { 8 | cacheName: 'start-url', 9 | expiration: { 10 | maxEntries: 1, 11 | maxAgeSeconds: 24 * 60 * 60, // 24 hours 12 | }, 13 | }, 14 | }, 15 | { 16 | urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i, 17 | handler: 'CacheFirst', 18 | options: { 19 | cacheName: 'google-fonts', 20 | expiration: { 21 | maxEntries: 4, 22 | maxAgeSeconds: 365 * 24 * 60 * 60, // 365 days 23 | }, 24 | }, 25 | }, 26 | { 27 | urlPattern: /\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i, 28 | handler: 'StaleWhileRevalidate', 29 | options: { 30 | cacheName: 'static-font-assets', 31 | expiration: { 32 | maxEntries: 4, 33 | maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days 34 | }, 35 | }, 36 | }, 37 | { 38 | urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i, 39 | handler: 'StaleWhileRevalidate', 40 | options: { 41 | cacheName: 'static-image-assets', 42 | expiration: { 43 | maxEntries: 64, 44 | maxAgeSeconds: 24 * 60 * 60, // 24 hours 45 | }, 46 | }, 47 | }, 48 | { 49 | urlPattern: /\.(?:js)$/i, 50 | handler: 'StaleWhileRevalidate', 51 | options: { 52 | cacheName: 'static-js-assets', 53 | expiration: { 54 | maxEntries: 16, 55 | maxAgeSeconds: 24 * 60 * 60, // 24 hours 56 | }, 57 | }, 58 | }, 59 | { 60 | urlPattern: /\.(?:css|less)$/i, 61 | handler: 'StaleWhileRevalidate', 62 | options: { 63 | cacheName: 'static-style-assets', 64 | expiration: { 65 | maxEntries: 16, 66 | maxAgeSeconds: 24 * 60 * 60, // 24 hours 67 | }, 68 | }, 69 | }, 70 | { 71 | urlPattern: /\.(?:json|xml|csv)$/i, 72 | handler: 'StaleWhileRevalidate', 73 | options: { 74 | cacheName: 'static-data-assets', 75 | expiration: { 76 | maxEntries: 16, 77 | maxAgeSeconds: 24 * 60 * 60, // 24 hours 78 | }, 79 | }, 80 | }, 81 | { 82 | urlPattern: /\/api\/.*$/i, 83 | handler: 'NetworkFirst', 84 | method: 'GET', 85 | options: { 86 | cacheName: 'apis', 87 | expiration: { 88 | maxEntries: 16, 89 | maxAgeSeconds: 24 * 60 * 60, // 24 hours 90 | }, 91 | networkTimeoutSeconds: 10, // fall back to cache if api does not response within 10 seconds 92 | }, 93 | }, 94 | { 95 | urlPattern: /^https:\/\/ik\.imagekit\.io\/.*/i, 96 | handler: 'StaleWhileRevalidate', 97 | method: 'GET', 98 | options: { 99 | cacheName: 'auralite-image-cdn', 100 | expiration: { 101 | maxEntries: 64, 102 | maxAgeSeconds: 24 * 60 * 60, // 24 hours 103 | }, 104 | }, 105 | }, 106 | { 107 | urlPattern: /^https:\/\/api\.auralite\.io\/api\/.*/i, 108 | handler: 'NetworkFirst', 109 | method: 'GET', 110 | options: { 111 | cacheName: 'apis', 112 | expiration: { 113 | maxEntries: 16, 114 | maxAgeSeconds: 24 * 60 * 60, // 24 hours 115 | }, 116 | networkTimeoutSeconds: 10, // fall back to cache if api does not response within 10 seconds 117 | }, 118 | }, 119 | { 120 | urlPattern: /\/api\/.*$/i, 121 | handler: 'NetworkFirst', 122 | method: 'POST', 123 | options: { 124 | cacheName: 'apis', 125 | expiration: { 126 | maxEntries: 16, 127 | maxAgeSeconds: 24 * 60 * 60, // 24 hours 128 | }, 129 | networkTimeoutSeconds: 10, // fall back to cache if api does not response within 10 seconds 130 | }, 131 | }, 132 | { 133 | urlPattern: /.*/i, 134 | handler: 'NetworkFirst', 135 | options: { 136 | cacheName: 'others', 137 | expiration: { 138 | maxEntries: 32, 139 | maxAgeSeconds: 24 * 60 * 60, // 24 hours 140 | }, 141 | networkTimeoutSeconds: 10, 142 | }, 143 | }, 144 | ] 145 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "node_modules", 4 | "paths": { 5 | "@/*": [ 6 | "../src/*" 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withPWA = require('next-pwa') 2 | const withSourceMaps = require('@zeit/next-source-maps')() 3 | const SentryWebpackPlugin = require('@sentry/webpack-plugin') 4 | 5 | const { NEXT_PUBLIC_SENTRY_DSN: SENTRY_DSN, SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN, NODE_ENV } = process.env 6 | 7 | process.env.SENTRY_DSN = SENTRY_DSN 8 | 9 | module.exports = withPWA( 10 | withSourceMaps({ 11 | env: { 12 | commitHash: process.env.VERCEL_GITHUB_COMMIT_SHA, 13 | }, 14 | experimental: { 15 | modern: true, 16 | optimizeFonts: true, 17 | optimizeImages: true, 18 | }, 19 | poweredByHeader: false, 20 | pwa: { 21 | disable: process.env.NODE_ENV === 'development', 22 | register: true, 23 | runtimeCaching: require('./cache.config'), 24 | dest: 'public', 25 | }, 26 | webpack: (config, options) => { 27 | if (!options.isServer) { 28 | config.resolve.alias['@sentry/node'] = '@sentry/browser' 29 | } 30 | 31 | if (SENTRY_DSN && SENTRY_ORG && SENTRY_PROJECT && SENTRY_AUTH_TOKEN && NODE_ENV === 'production') { 32 | config.plugins.push( 33 | new SentryWebpackPlugin({ 34 | include: '.next', 35 | ignore: ['node_modules'], 36 | urlPrefix: '~/_next', 37 | release: options.buildId, 38 | }) 39 | ) 40 | } 41 | 42 | return config 43 | }, 44 | }) 45 | ) 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auralite-web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "eslint --fix ." 10 | }, 11 | "dependencies": { 12 | "@sentry/browser": "5.20.1", 13 | "@sentry/node": "^5.20.1", 14 | "@sentry/webpack-plugin": "^1.12.0", 15 | "@stripe/stripe-js": "^1.8.0", 16 | "@tailwindcss/typography": "^0.2.0", 17 | "@tailwindcss/ui": "^0.5.0", 18 | "@zeit/next-source-maps": "0.0.4-canary.1", 19 | "axios": "^0.19.2", 20 | "chrome-aws-lambda": "^5.2.0", 21 | "date-fns": "^2.15.0", 22 | "fast-glob": "^3.2.4", 23 | "fathom-client": "^3.0.0", 24 | "gray-matter": "^4.0.2", 25 | "js-cookie": "^2.2.1", 26 | "next": "9.5.2", 27 | "next-cookies": "^2.0.3", 28 | "next-mdx-remote": "^0.6.0", 29 | "next-pwa": "^3.1.1", 30 | "nprogress": "^0.2.0", 31 | "pipeline-js": "^1.0.2", 32 | "postcss-100vh-fix": "^0.1.1", 33 | "puppeteer-core": "^5.2.1", 34 | "react": "^16.13.1", 35 | "react-circular-progressbar": "^2.0.3", 36 | "react-dom": "^16.13.1", 37 | "react-easy-swipe": "^0.0.18", 38 | "react-intersection-observer": "^8.26.2", 39 | "react-medium-image-zoom": "^4.3.1", 40 | "react-portal": "^4.2.1", 41 | "react-string-replace": "^0.4.4", 42 | "react-transition-group": "^4.4.1", 43 | "sass": "^1.26.10", 44 | "swr": "^0.3.0", 45 | "tailwindcss": "^1.6.2" 46 | }, 47 | "devDependencies": { 48 | "babel-eslint": "^10.1.0", 49 | "eslint": "^7.6.0", 50 | "eslint-config-prettier": "^6.11.0", 51 | "eslint-plugin-jsx-a11y": "^6.3.1", 52 | "eslint-plugin-prettier": "^3.1.4", 53 | "eslint-plugin-react": "^7.20.5", 54 | "file-loader": "^6.0.0", 55 | "postcss-selector-parser": "^6.0.2", 56 | "prettier": "^2.0.5" 57 | }, 58 | "resolutions": { 59 | "webpack": "^5.0.0-beta.25" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['tailwindcss', 'autoprefixer', 'postcss-100vh-fix'], 3 | } 4 | -------------------------------------------------------------------------------- /public/.well-known/assetlinks.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "relation": ["delegate_permission/common.handle_all_urls"], 3 | "target": { 4 | "namespace": "android_app", 5 | "package_name": "io.auralite.mobile", 6 | "sha256_cert_fingerprints": [ 7 | "50:44:E0:9F:0A:0F:2E:CA:A1:E8:73:72:12:F1:C6:8B:93:E8:DA:46:62:89:00:13:BA:DF:BF:18:51:17:04:C8" 8 | ] 9 | } 10 | }] -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/Verveine/Verveine.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/fonts/Verveine/Verveine.eot -------------------------------------------------------------------------------- /public/fonts/Verveine/Verveine.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/fonts/Verveine/Verveine.ttf -------------------------------------------------------------------------------- /public/fonts/Verveine/Verveine.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/fonts/Verveine/Verveine.woff -------------------------------------------------------------------------------- /public/fonts/Verveine/Verveine.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/fonts/Verveine/Verveine.woff2 -------------------------------------------------------------------------------- /public/img/articles/progress-update-1/card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/articles/progress-update-1/card.jpg -------------------------------------------------------------------------------- /public/img/articles/progress-update-2/card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/articles/progress-update-2/card.jpg -------------------------------------------------------------------------------- /public/img/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/avatar.jpg -------------------------------------------------------------------------------- /public/img/bg-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/bg-dark.png -------------------------------------------------------------------------------- /public/img/card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/card.jpg -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #603cba 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auralite/web/a51a8f4821458c819ec9df7b2bd34759cc7607b3/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | Created by potrace 1.11, written by Peter Selinger 2001-2013 -------------------------------------------------------------------------------- /public/img/icons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Auralite", 3 | "short_name": "Auralite", 4 | "icons": [ 5 | { 6 | "src": "/img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#6875f5", 17 | "background_color": "#6875f5", 18 | "display": "standalone", 19 | "prefer_related_applications": true, 20 | "related_applications": [ 21 | { 22 | "platform": "play", 23 | "id": "io.auralite.mobile" 24 | } 25 | ], 26 | "start_url": "https://auralite.io/home" 27 | } 28 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: -------------------------------------------------------------------------------- /src/articles/progress-update-1.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | subheading: "Auralite Bi-Weekly Update #1" 3 | title: Going Open-Source & A Peek of the Onboarding 4 | description: The first Auralite progress update, in which I talk about the onboarding, going open source and more. 5 | date: "2020-06-30T18:05:31Z" 6 | image: /img/articles/progress-update-1/card.jpg 7 | --- 8 | 9 | **👋 Hi! I'm Miguel Piedrafita, the maker behind Auralite.** After [announcing Auralite a few days ago](https://twitter.com/m1guelpf/status/1275815147248979968), I've been hard at work adding new features and fixing bugs, getting the platform ready for the beta. Here's what's new. 10 | 11 | ## A peek at the onboarding 12 | 13 | I'm sure you can't wait to see how Auralite looks, so I decided to record **a quick video showing how creating an account feels like**. The design is still a work in progress, but the structure is there. 14 | 15 | 16 | 17 | Keep in mind the final product will include more documentation regarding why each step is required, and that the ordering isn't final. 18 | 19 | ## Platform updates: Open Source! 20 | 21 | *Openness is a big part of Auralite*, and our Open API is the best example of this value. To illustrate the capabilites of this API and create a reference others can follow when building custom clients or integrations, I've decided to extract the Auralite frontend from the backend and release it as open-source. That's right, **the Auralite frontend will be completely open-source, and make use of the same APIs you have access to**! 22 | 23 | I've been hard at work migrating the web client to use Next.js. You can check out my progress (and contribute) [on GitHub](https://github.com/auralite/web). 24 | 25 | And, while we're in the topic of clients, [Matthew Gleich](https://twitter.com/MattGleich) is working on an **unofficial mobile client** with Flutter! It's still on its early stages, but you can see his code [on GitHub](https://github.com/Matt-Gleich/auralite-mobile/). 26 | 27 | ## Thank You 28 | 29 | I wanted to take a moment to **thank everyone that has decided to give Auralite a chance**. The reception has been amazing, and I'll try to get everyone on the platform as soon as possible! 30 | 31 | The first wave of invites (a really small one, only 10 people to start testing out stuff) is already out, but **a second wave will be going out in a few days** and will include a few more people. 32 | 33 | If you haven't already, make sure to sign up for early-access below so we can send you your Auralite invite when everything's ready. -------------------------------------------------------------------------------- /src/articles/progress-update-2.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | subheading: "Auralite Bi-Weekly Update #2" 3 | title: A Mobile App, New Features & Future Plans. 4 | description: A recap of my last two weeks of work on Auralite, including migrating to Next.js, getting feature-parity with other social networks, going mobile and adding more features. 5 | date: "2020-07-16T05:00:00Z" 6 | image: /img/articles/progress-update-2/card.jpg 7 | --- 8 | 9 | **👋 Hi! I'm Miguel Piedrafita, the maker behind Auralite.** I've been hard at work on Auralite since [the last update](https://auralite.io/blog/progress-update-1). Here's a recap of everything I got done on these two weeks, and what I have planned for the next two. 10 | 11 | ## 🛠 Migrating to Next.js 12 | 13 | I started building Auralite with the stack I'm most used to, a monolithic PHP app powered by Laravel. A week into building the app, however, I decided to switch to a different model, with an open-source SPA powered by our public API. This not only served our _openness_ goal and set an example for developing custom Auralite clients, but made the experience much smoother. 14 | 15 | Anyway, I had to migrate all my existing, backend-supported code to a Next.js app and figure out how to handle authentication, state, and communication. This turned out to be easier than I expected, and I managed to get most of the migration done in two days. 16 | 17 | ## 📱 A mobile app 18 | 19 | With the Next.js migration out of the way, I started brainstorming ideas for **providing a great mobile experience**. After looking into React Native and Flutter, I decided to roll with a Progressive Web App, since it'd allow me to get something out in the least time. The initial setup was pretty easy, as the frontend was already fully-responsive, and a Webpack plugin took me most of the way to where I wanted. 20 | 21 | Once that was done, I started looking into distribution. iOS seems to be a lost cause regarding PWAs, but they provide decent support and allow installation via Safari, which is something. Android, on the other hand, allows you to package your PWA into a native Android app and release it on the Play Store, [which is just what I did](https://play.google.com/store/apps/details?id=io.auralite.mobile). 22 | 23 | ## 🌐 Network feature parity 24 | 25 | Which is the nerdy way of saying that **Auralite now has enough features to be called a proper social network**. You can post, reply, add images to your posts, edit your profile, view others' profiles, etc. (there's no following right now since there aren't many people, but that'll be added down the line) It also has search, mentions and notifications. This won't seem very exciting, but it was required so I could start working on the cool stuff, *the things that make Auralite different* from every other platform out there. 26 | 27 | Oh, and **profiles are now public**! The whole platform required login before, but you can now view anyone's profile without an Auralite account. [Here's mine](https://auralite.io/miguel). 28 | 29 | ## ✨ New Auralite features 30 | 31 | I had some extra time after all that work and started sketching out some of those Auralite-unique features. Since I was gonna make profiles public, I started by **allowing users to control who can view their posts** on a per-post basis. You can only choose between Everyone and Auralite users right now, but I plan to add more options down the line, as well as a separate toggle for **controlling who can interact with posts**. 32 | 33 | Something from the announcement that resonated with many people was the promise of a platform that embraced open standards, like RSS. With this in mind, I didn't stop at adding **public RSS feeds for each Auralite user** (point your feedreader at [my profile](https://auralite.io/miguel) to try it out), but also added a **private feed with your personal timeline**, so you can read your Auralite feed alongside your favourite blogs. 34 | 35 | Finally, I wanted Auralite profiles to look great when shared, so I made some **dynamic cards that appear when sharing your profile** on sites like Twitter, Telegram, or Slack. [Here's a tweet showing off how it looks](https://twitter.com/m1guelpf/status/1282493331885510656). 36 | 37 | ## 🔮 Plans for the next cycle 38 | 39 | I'm organizing development in **two-week development cycles**. This means I disappear into the coding cave for two weeks, occasionally coming out to share what I'm working on (on [Twitter](https://twitter.com/m1guelpf) and [Auralite](https://auralite.io/miguel)), then write an email to the early-access list and a big recap article like the one you're reading right now. I'm also going to start **dropping a batch of invites at the end of each cycle, starting with this one**. 40 | 41 | The first thing I want to add on the next cycle is **cross-posting to Twitter**. This will be an optional feature and might even be removed in the future, but it's meant to allow everyone to **start using Auralite without losing your existing Twitter audience**. It will also allow me to spend less time on Twiter, which is always a good thing. 42 | 43 | Once that's done, I want to **rework notifications**. They work as we've grown to expect notifications to work (someone mentions, you get a notification), but I want to experiment with ways to *make them less attention-grabbing*, like batching or delaying them. 44 | 45 | Finally, I want to start working on an improved version of the feed. **I want to make it impossible for anyone to get stuck scrolling and scrolling down the feed**, which I constantly find myself doing on Twitter. I have a few ideas on how to improve this, but I'll save those for the next update. 46 | 47 | ## 🙌 Thank You 48 | 49 | Finally, I wanted to take a moment to **thank everyone excited to get their hands into the platform**. Auralite started as my dream for *a better social network*, and it's awesome to see others share this dream. 50 | 51 | If you have any questions, feedback, or ideas, feel free to [email me](mailto:miguel@auralite.io), and I'll be happy to answer you. **Let's make something great, together!** 52 | 53 | *~ Miguel Piedrafita* -------------------------------------------------------------------------------- /src/components/App/Alert.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import Transition from '../Global/Transition' 3 | import { CheckCircleOutline, CrossSolid } from './Icon' 4 | 5 | const Alert = ({ title, body }) => { 6 | const [visible, setVisible] = useState(true) 7 | 8 | setTimeout(() => setVisible(false), 3000) 9 | 10 | return ( 11 | 12 |
13 |
14 |
15 |
16 |
17 | 18 |
19 |
20 |

{title}

21 |

{body}

22 |
23 |
24 | 27 |
28 |
29 |
30 |
31 |
32 |
33 | ) 34 | } 35 | 36 | export default Alert 37 | -------------------------------------------------------------------------------- /src/components/App/AlertManager.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import AlertContext from '../../context/alerts' 3 | import Alert from './Alert' 4 | import { TransitionGroup } from 'react-transition-group' 5 | 6 | const AlertManager = ({ children }) => { 7 | const [alerts, setAlerts] = useState([]) 8 | 9 | return ( 10 | <> 11 | {children} 12 | {alerts && ( 13 |
14 | 15 | {alerts.map(({ title, body }, i) => ( 16 | 17 | ))} 18 | 19 |
20 | )} 21 | 22 | ) 23 | } 24 | 25 | export default AlertManager 26 | -------------------------------------------------------------------------------- /src/components/App/Avatar.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, memo } from 'react' 2 | import { CircularProgressbar, buildStyles } from 'react-circular-progressbar' 3 | import Client from '../../utils/Client' 4 | import Skeleton from './Skeleton' 5 | import useTailwind from '../../hooks/tailwind' 6 | import { UploadSolid } from './Icon' 7 | 8 | const Avatar = ({ src, className = '', sizeClasses, lazy, children }) => { 9 | const [width, height] = useTailwind(sizeClasses, ['width', 'height']) 10 | const [avatarUrl, setAvatarUrl] = useState(useCDN(src, width, height)) 11 | 12 | useEffect(() => { 13 | setAvatarUrl(useCDN(src, width, height)) 14 | }, [src]) 15 | 16 | return ( 17 |
18 | {avatarUrl ? : } 19 | {children} 20 |
21 | ) 22 | } 23 | 24 | export const UploadableAvatar = ({ shouldAllowUploads = true, onChange, src, ...props }) => { 25 | const [file, setFile] = useState(null) 26 | const [source, setSource] = useState(src) 27 | const [progress, setProgress] = useState(0) 28 | 29 | useEffect(() => { 30 | setSource(src) 31 | }, [src]) 32 | 33 | useEffect(() => { 34 | if (!file) return 35 | 36 | Client.uploadFile({ file, progress: (progress) => setProgress(Math.round(progress * 100)) }) 37 | .catch(() => alert('Something went wrong when uploading your profile pic')) 38 | .then((response) => onChange(`${response.key}.${response.extension}`)) 39 | }, [file]) 40 | 41 | useEffect(() => { 42 | if (progress !== 100) return 43 | 44 | setTimeout(() => { 45 | setSource(URL.createObjectURL(file)) 46 | 47 | setProgress(0) 48 | }, 1000) 49 | }, [progress]) 50 | 51 | return ( 52 | 53 | {shouldAllowUploads && ( 54 | <> 55 |
56 | 57 |
58 | 59 | 62 | 63 | )} 64 |
65 | ) 66 | } 67 | 68 | const useCDN = (src, width, height) => { 69 | if (!src?.startsWith('https://auralite.s3.eu-west-2.amazonaws.com/')) return src 70 | 71 | return `https://ik.imagekit.io/auralite/tr:w-${parseFloat(width.split('rem')[0]) * 24},h-${parseFloat(height.split('rem')[0]) * 24}/${src.split('https://auralite.s3.eu-west-2.amazonaws.com/', 2)[1]}` 72 | } 73 | 74 | export default memo(Avatar) 75 | -------------------------------------------------------------------------------- /src/components/App/ClientOnly.js: -------------------------------------------------------------------------------- 1 | const { useState, useEffect } = require('react') 2 | 3 | const ClientOnly = ({ children }) => { 4 | if (isSSR()) return null 5 | 6 | return children 7 | } 8 | 9 | export const isSSR = () => { 10 | const [hasMounted, setHasMounted] = useState(false) 11 | 12 | useEffect(() => { 13 | setHasMounted(true) 14 | }, []) 15 | 16 | return !hasMounted 17 | } 18 | 19 | export default ClientOnly 20 | -------------------------------------------------------------------------------- /src/components/App/Compose.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, forwardRef, memo } from 'react' 2 | import Client from '../../utils/Client' 3 | import LoadingButton from './LoadingButton' 4 | import Avatar from './Avatar' 5 | import ImageGrid, { useImageGrid } from './ImageGrid' 6 | import useUser from '@/hooks/user' 7 | import { ImageOutline, GlobeOutline, UserCircleOutline } from './Icon' 8 | 9 | const Compose = forwardRef(({ replyTo, onPost = () => {} }, ref) => { 10 | const { user } = useUser() 11 | 12 | const [loading, setLoading] = useState(false) 13 | const [error, setError] = useState(null) 14 | 15 | const [post, setPost] = useState('') 16 | const remainingChars = 300 - post.length 17 | 18 | const { uploaderSettings, gridSettings, showGrid, hasPendingImages, images, cleanupImages } = useImageGrid() 19 | const [privacy, setPrivacy] = useState('public') 20 | 21 | const updatePost = (content) => { 22 | setPost(content.replace(/\n{3,}/m, '\n\n')) 23 | 24 | setError(null) 25 | } 26 | 27 | useEffect(() => { 28 | setPrivacy(replyTo?.privacy ?? 'public') 29 | }, [replyTo?.privacy]) 30 | 31 | const submitForm = (event) => { 32 | event.preventDefault() 33 | 34 | setLoading(true) 35 | 36 | Client.createPost({ post: post.trim(), privacy, reply_to: replyTo?.id, images }) 37 | .then((post) => { 38 | onPost(post) 39 | setLoading(false) 40 | setError(null) 41 | setPost('') 42 | cleanupImages() 43 | }) 44 | .catch((error) => { 45 | if (!error.response?.data?.errors) return alert('Something went wrong when creating your post.') 46 | 47 | setError(error.response.data.errors.content[0]) 48 | setLoading(false) 49 | }) 50 | } 51 | 52 | return ( 53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |