mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore: add stencil-supabase to example/stencil-supabase
This commit is contained in:
1 parent
b06114ac5c
commit
ae9a0bfa50
45 files changed
+9807
No files matched your search
@@ -0,0 +1,15 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@stencil/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@stencil/async-methods": "error",
|
||||
"@stencil/ban-prefix": ["error", ["stencil", "stnl", "st"]],
|
||||
"@stencil/decorators-context": "error",
|
||||
"@stencil/decorators-style": [
|
||||
"error",
|
||||
{
|
||||
"prop": "inline",
|
||||
"state": "inline",
|
||||
"element": "inline",
|
||||
"event": "inline",
|
||||
"method": "multiline",
|
||||
"watch": "multiline",
|
||||
"listen": "multiline"
|
||||
}
|
||||
],
|
||||
"@stencil/element-type": "error",
|
||||
"@stencil/host-data-deprecated": "error",
|
||||
"@stencil/methods-must-be-public": "error",
|
||||
"@stencil/no-unused-watch": "error",
|
||||
"@stencil/own-methods-must-be-private": "off",
|
||||
"@stencil/own-props-must-be-private": "off",
|
||||
"@stencil/props-must-be-public": "off",
|
||||
"@stencil/props-must-be-readonly": "off",
|
||||
"@stencil/render-returns-host": "error",
|
||||
"@stencil/reserved-member-names": "error",
|
||||
"@stencil/single-export": "error",
|
||||
"@stencil/strict-boolean-conditions": "off",
|
||||
"@stencil/strict-mutable": "off",
|
||||
"react/jsx-no-bind": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/macos,windows,linux,code,vscode,node
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,linux,code,vscode,node
|
||||
|
||||
### Code ###
|
||||
.vscode/*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
*.code-workspace
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-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/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# 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 variables file
|
||||
.env
|
||||
.env.test
|
||||
.env*.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
dist/
|
||||
!www/favicon.ico
|
||||
www/
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
### vscode ###
|
||||
!.vscode/settings.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/macos,windows,linux,code,vscode,node
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"jsxSingleQuote": false,
|
||||
"quoteProps": "consistent",
|
||||
"printWidth": 180,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"useTabs": false
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Stencil & Supabase Starter
|
||||
|
||||
This is a boilerplate for creating a WebApp using [Stencil](https://github.com/ionic-team/stencil) + [Supabase](https://supabase.io/) + [Stencil Store](https://stenciljs.com/docs/stencil-store).
|
||||
|
||||
<img src="./login-page.png" alt="Login Page">
|
||||
|
||||
## Features 📋
|
||||
|
||||
- Supabase setup
|
||||
- Stencil Store setup
|
||||
- Login / Register / Logout functionality (Basic one)
|
||||
- Protecting routes (router-guard) that are accessed after login
|
||||
|
||||
## Getting Started 🚀
|
||||
|
||||
To start using this boilerplate, clone this repo to a new directory:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/ftonato/stencil-supabase.git
|
||||
```
|
||||
|
||||
and run:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
## Usage 🚊
|
||||
|
||||
Setup Supabase configuration
|
||||
|
||||
Please setup _Supabase_ configuration in the file
|
||||
|
||||
```
|
||||
src -> config -> config.ts
|
||||
```
|
||||
|
||||
```js
|
||||
const supabaseConfig = {
|
||||
supabaseUrl: '',
|
||||
supabaseKey: ''
|
||||
};
|
||||
```
|
||||
|
||||
You can find more about _Supabase Client_ config [on the official documentation page](https://supabase.io/docs/client/initializing).
|
||||
|
||||
To start a project, run:
|
||||
```sh
|
||||
npm run start
|
||||
```
|
||||
|
||||
To build the app for production, run:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
To run the unit tests once, run:
|
||||
|
||||
```sh
|
||||
npm run test
|
||||
```
|
||||
|
||||
To run the unit tests and watch for file changes during development, run:
|
||||
|
||||
```sh
|
||||
npm run test.watch
|
||||
```
|
||||
|
||||
## Project structure 🗄️
|
||||
|
||||
You can check the details of the [structure of our project through this file](./STRUCTURE.md)
|
||||
|
||||
## Contributing 👏
|
||||
- [Pull requests](https://github.com/ftonato/stencil-supabase/pulls) and 🌟 stars are always welcome.
|
||||
|
||||
## License
|
||||
[MIT ©](LICENSE)
|
||||
@@ -0,0 +1,51 @@
|
||||
## Project structure 🗄️
|
||||
|
||||
### Service
|
||||
This class will handle application authenticate features to supabase.
|
||||
|
||||
```
|
||||
src -> services -> auth.service.ts -> Class SupabaseAuthService
|
||||
```
|
||||
You can create more classes to group a functionality.
|
||||
|
||||
### Store
|
||||
Application store
|
||||
```
|
||||
src -> store -> app.store.ts
|
||||
```
|
||||
|
||||
### Interfaces
|
||||
Interfaces used in the application
|
||||
```
|
||||
src -> interface -> interface.ts
|
||||
```
|
||||
### Utility
|
||||
Utility for parsing application messages and holding form configurations
|
||||
```
|
||||
src -> util -> util.ts
|
||||
```
|
||||
### Configuration
|
||||
Application level configurations
|
||||
```
|
||||
src -> config -> config.ts
|
||||
```
|
||||
### Assets
|
||||
Application level assets
|
||||
```
|
||||
src -> assets
|
||||
```
|
||||
### Components
|
||||
Application level components
|
||||
|
||||
```
|
||||
src -> components ->
|
||||
1. <app-flash-message> for showing alert messages
|
||||
2. <app-home> Dashboard
|
||||
3. <app-root> entry component of the application
|
||||
5. <page> for showing pages
|
||||
6. <auth> -> having login/register components
|
||||
```
|
||||
|
||||
### Styling 💀
|
||||
|
||||
I have used `scss` for styling components. Ths boilerplate have a basic design i.e it focus more on configuring and using supabase in stencil web app along with stencil store. You can customize the looks as per your needs.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
Generated
+7592
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "stencil-supabase-starter",
|
||||
"private": false,
|
||||
"version": "0.0.1",
|
||||
"description": "Welcome to the Stencil & Supabase starter App. You can use this starter to build entire apps all with web components using Stencil & Supabase",
|
||||
"scripts": {
|
||||
"build": "stencil build",
|
||||
"start": "stencil build --dev --watch --serve",
|
||||
"test": "stencil test --spec --e2e",
|
||||
"test.watch": "stencil test --spec --e2e --watchAll",
|
||||
"generate": "stencil generate",
|
||||
"lint": "eslint src/**/*{.ts,.tsx}"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stencil/core": "^1.17.3",
|
||||
"@stencil/eslint-plugin": "^0.3.1",
|
||||
"@stencil/router": "^1.0.1",
|
||||
"@stencil/sass": "^1.3.2",
|
||||
"@stencil/store": "^1.3.0",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/puppeteer": "2.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^3.10.1",
|
||||
"@typescript-eslint/parser": "^3.10.1",
|
||||
"eslint": "^7.7.0",
|
||||
"eslint-plugin-react": "^7.20.6",
|
||||
"jest": "26.0.1",
|
||||
"jest-cli": "26.0.1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Ademílson F. Tonato - @ftonato",
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^1.3.3",
|
||||
"puppeteer": "^5.2.1"
|
||||
},
|
||||
"keywords": [
|
||||
"web components",
|
||||
"components",
|
||||
"stencil",
|
||||
"supabase",
|
||||
"supabase stencil",
|
||||
"stencil store"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,64 @@
|
||||
@import './helpers/reset';
|
||||
@import './helpers/variables';
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@200;400;600;700&display=swap');
|
||||
|
||||
body {
|
||||
align-items: center;
|
||||
background-color: $clr-bg-main;
|
||||
color: $clr-text-main;
|
||||
display: flex;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
|
||||
font-family: $fnt-text-main;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
padding: 2.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: $clr-text-headline;
|
||||
font-size: 2.5em;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 180%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $clr-text-link;
|
||||
font-weight: 600;
|
||||
text-decoration: inherit;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $clr-bg-quote-hover;
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: $clr-bg-quote-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span {
|
||||
&.stencil {
|
||||
color: $clr-bg-quote !important;
|
||||
}
|
||||
|
||||
&.supabase {
|
||||
color: #65d9a5 !important
|
||||
}
|
||||
}
|
||||
|
||||
section.app-section {
|
||||
margin: 0 auto 100px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
$clr-bg-main: #f8f8f2;
|
||||
$clr-bg-box: #fff;
|
||||
$clr-bg-quote: #4c48fe;
|
||||
$clr-bg-quote-hover: #0f1396;
|
||||
$clr-text-main: #bebebe;
|
||||
$clr-text-headline: #131125;
|
||||
$clr-text-link: #a0a4e3;
|
||||
$clr-bg-box-alert: #f44336;
|
||||
$clr-bd-box-alert: #dc3b2f;
|
||||
$clr-text-alert: #fff;
|
||||
|
||||
$fnt-text-main: 'Source Sans Pro', sans-serif;
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* This is an autogenerated file created by the Stencil compiler.
|
||||
* It contains typing information for all components that exist in this project.
|
||||
*/
|
||||
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
|
||||
import { RouterHistory } from "@stencil/router";
|
||||
export namespace Components {
|
||||
interface AppAuthLogin {
|
||||
/**
|
||||
* history instance.
|
||||
*/
|
||||
"history": RouterHistory;
|
||||
}
|
||||
interface AppAuthRegister {
|
||||
/**
|
||||
* history instance.
|
||||
*/
|
||||
"history": RouterHistory;
|
||||
}
|
||||
interface AppFlashMessage {
|
||||
/**
|
||||
* alert closable.
|
||||
*/
|
||||
"closable": boolean;
|
||||
/**
|
||||
* Show/hide alert message
|
||||
*/
|
||||
"show": boolean;
|
||||
/**
|
||||
* Message type
|
||||
*/
|
||||
"type": string;
|
||||
}
|
||||
interface AppHome {
|
||||
}
|
||||
interface AppPageHome {
|
||||
}
|
||||
interface AppRoot {
|
||||
/**
|
||||
* history instance.
|
||||
*/
|
||||
"history": RouterHistory;
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
interface HTMLAppAuthLoginElement extends Components.AppAuthLogin, HTMLStencilElement {
|
||||
}
|
||||
var HTMLAppAuthLoginElement: {
|
||||
prototype: HTMLAppAuthLoginElement;
|
||||
new (): HTMLAppAuthLoginElement;
|
||||
};
|
||||
interface HTMLAppAuthRegisterElement extends Components.AppAuthRegister, HTMLStencilElement {
|
||||
}
|
||||
var HTMLAppAuthRegisterElement: {
|
||||
prototype: HTMLAppAuthRegisterElement;
|
||||
new (): HTMLAppAuthRegisterElement;
|
||||
};
|
||||
interface HTMLAppFlashMessageElement extends Components.AppFlashMessage, HTMLStencilElement {
|
||||
}
|
||||
var HTMLAppFlashMessageElement: {
|
||||
prototype: HTMLAppFlashMessageElement;
|
||||
new (): HTMLAppFlashMessageElement;
|
||||
};
|
||||
interface HTMLAppHomeElement extends Components.AppHome, HTMLStencilElement {
|
||||
}
|
||||
var HTMLAppHomeElement: {
|
||||
prototype: HTMLAppHomeElement;
|
||||
new (): HTMLAppHomeElement;
|
||||
};
|
||||
interface HTMLAppPageHomeElement extends Components.AppPageHome, HTMLStencilElement {
|
||||
}
|
||||
var HTMLAppPageHomeElement: {
|
||||
prototype: HTMLAppPageHomeElement;
|
||||
new (): HTMLAppPageHomeElement;
|
||||
};
|
||||
interface HTMLAppRootElement extends Components.AppRoot, HTMLStencilElement {
|
||||
}
|
||||
var HTMLAppRootElement: {
|
||||
prototype: HTMLAppRootElement;
|
||||
new (): HTMLAppRootElement;
|
||||
};
|
||||
interface HTMLElementTagNameMap {
|
||||
"app-auth-login": HTMLAppAuthLoginElement;
|
||||
"app-auth-register": HTMLAppAuthRegisterElement;
|
||||
"app-flash-message": HTMLAppFlashMessageElement;
|
||||
"app-home": HTMLAppHomeElement;
|
||||
"app-page-home": HTMLAppPageHomeElement;
|
||||
"app-root": HTMLAppRootElement;
|
||||
}
|
||||
}
|
||||
declare namespace LocalJSX {
|
||||
interface AppAuthLogin {
|
||||
/**
|
||||
* history instance.
|
||||
*/
|
||||
"history"?: RouterHistory;
|
||||
}
|
||||
interface AppAuthRegister {
|
||||
/**
|
||||
* history instance.
|
||||
*/
|
||||
"history"?: RouterHistory;
|
||||
}
|
||||
interface AppFlashMessage {
|
||||
/**
|
||||
* alert closable.
|
||||
*/
|
||||
"closable"?: boolean;
|
||||
/**
|
||||
* Show/hide alert message
|
||||
*/
|
||||
"show"?: boolean;
|
||||
/**
|
||||
* Message type
|
||||
*/
|
||||
"type"?: string;
|
||||
}
|
||||
interface AppHome {
|
||||
}
|
||||
interface AppPageHome {
|
||||
}
|
||||
interface AppRoot {
|
||||
/**
|
||||
* history instance.
|
||||
*/
|
||||
"history"?: RouterHistory;
|
||||
}
|
||||
interface IntrinsicElements {
|
||||
"app-auth-login": AppAuthLogin;
|
||||
"app-auth-register": AppAuthRegister;
|
||||
"app-flash-message": AppFlashMessage;
|
||||
"app-home": AppHome;
|
||||
"app-page-home": AppPageHome;
|
||||
"app-root": AppRoot;
|
||||
}
|
||||
}
|
||||
export { LocalJSX as JSX };
|
||||
declare module "@stencil/core" {
|
||||
export namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
"app-auth-login": LocalJSX.AppAuthLogin & JSXBase.HTMLAttributes<HTMLAppAuthLoginElement>;
|
||||
"app-auth-register": LocalJSX.AppAuthRegister & JSXBase.HTMLAttributes<HTMLAppAuthRegisterElement>;
|
||||
"app-flash-message": LocalJSX.AppFlashMessage & JSXBase.HTMLAttributes<HTMLAppFlashMessageElement>;
|
||||
"app-home": LocalJSX.AppHome & JSXBase.HTMLAttributes<HTMLAppHomeElement>;
|
||||
"app-page-home": LocalJSX.AppPageHome & JSXBase.HTMLAttributes<HTMLAppPageHomeElement>;
|
||||
"app-root": LocalJSX.AppRoot & JSXBase.HTMLAttributes<HTMLAppRootElement>;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
describe('app-flash-message', () => {
|
||||
it('renders', async () => {
|
||||
const page = await newE2EPage();
|
||||
|
||||
await page.setContent('<app-flash-message></app-flash-message>');
|
||||
const element = await page.find('app-flash-message');
|
||||
expect(element).toHaveClass('hydrated');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
/* The alert message box */
|
||||
.app-alert {
|
||||
background-color: $clr-bg-box-alert;
|
||||
border: 1px solid $clr-bd-box-alert;
|
||||
border-radius: 3px;
|
||||
color: $clr-text-alert;
|
||||
margin-bottom: 6px;
|
||||
margin-top: 6px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
|
||||
/* The close button */
|
||||
.app-close-btn {
|
||||
color: $clr-text-alert;
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
line-height: 15px;
|
||||
margin-left: 15px;
|
||||
transition: 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Component for showing flash messages.
|
||||
*
|
||||
* @component AppFlashMessage
|
||||
*/
|
||||
|
||||
import { Component, h, Prop, Watch } from '@stencil/core';
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'app-flash-message',
|
||||
styleUrl: 'app-flash-message.scss'
|
||||
})
|
||||
export class AppFlashMessage {
|
||||
|
||||
/**
|
||||
* Message type
|
||||
*/
|
||||
@Prop() type: string = '';
|
||||
|
||||
/**
|
||||
* alert closable.
|
||||
*/
|
||||
@Prop() closable: boolean = true;
|
||||
|
||||
/**
|
||||
* Show/hide alert message
|
||||
*/
|
||||
@Prop({ mutable: true, reflect: true }) show = true;
|
||||
|
||||
@Watch('show')
|
||||
handleShowChange() {
|
||||
if (this.show === false) {
|
||||
this.hideMessage();
|
||||
} else {
|
||||
this.showMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide message
|
||||
*
|
||||
* @returns {(null | void)}
|
||||
*/
|
||||
async hideMessage() {
|
||||
if (!this.show) {
|
||||
return null;
|
||||
}
|
||||
this.show = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show message
|
||||
*
|
||||
* @returns {(null | void)}
|
||||
*/
|
||||
async showMessage() {
|
||||
if (this.show) {
|
||||
return null;
|
||||
}
|
||||
this.show = true;
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
// Show on init if open
|
||||
if (this.show) {
|
||||
this.showMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render component <app-flash-message>
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
class={{
|
||||
'app-alert': true,
|
||||
'success': this.type === 'success',
|
||||
'hidden': this.show == false
|
||||
}}
|
||||
role="alert"
|
||||
aria-hidden={this.show == false ? true : false}
|
||||
>
|
||||
{this.closable && (<span class="app-close-btn" onClick={() => this.hideMessage()}>×</span>)}
|
||||
<slot></slot>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
describe('app-home', () => {
|
||||
it('renders', async () => {
|
||||
const page = await newE2EPage();
|
||||
await page.setContent('<app-home></app-home>');
|
||||
|
||||
const element = await page.find('app-home');
|
||||
expect(element).toHaveClass('hydrated');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
.home-box {
|
||||
background-color: $clr-bg-box;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.025);
|
||||
display: flex;
|
||||
height: 60vh;
|
||||
min-height: 50em;
|
||||
overflow: hidden;
|
||||
width: 75em;
|
||||
|
||||
.home-box-subbox {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: center;
|
||||
justify-content: center;
|
||||
padding: 4em 6em;
|
||||
}
|
||||
|
||||
.home-box-container {
|
||||
.home-box-image {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Component for showing user dashboard after login.
|
||||
*
|
||||
* @component AppHome
|
||||
*/
|
||||
|
||||
import { Component, h } from '@stencil/core';
|
||||
import { AppState } from '../../store/app.store';
|
||||
import { appConfig } from '../../config/config';
|
||||
|
||||
@Component({
|
||||
tag: 'app-home',
|
||||
styleUrl: 'app-home.scss',
|
||||
|
||||
})
|
||||
export class AppHome {
|
||||
|
||||
/**
|
||||
* Render component <app-home>
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<section class='app-home'>
|
||||
<stencil-route-title pageTitle={appConfig.pageTitle.dashboard} />
|
||||
|
||||
<div class="home-box">
|
||||
<div class="home-box-subbox">
|
||||
<div class="home-box-container">
|
||||
<h1>Welcome to the <span class="stencil">Stencil</span> & <span class="supabase">Supabase</span> dashboard</h1>
|
||||
|
||||
<div class="home-box-image">
|
||||
<img src="./assets/images/supabase.png" />
|
||||
</div>
|
||||
|
||||
<h2>Hi{' '}
|
||||
<span class="user-name">
|
||||
{AppState.user ? AppState.user.email : null}
|
||||
</span>,
|
||||
</h2>
|
||||
|
||||
<p>Welcome to the {appConfig.name} dashboard.
|
||||
<br />
|
||||
This app demonstrate <strong>Login</strong>, <strong>Register</strong>, <strong>Logout</strong> features using Supabase + Stencil + Stencil Store as state management tool.</p>
|
||||
<p>
|
||||
You can read more about the technologies used here: <a rel="external" href="https://github.com/ionic-team/stencil" target="_blank">Stencil</a>{', '}
|
||||
<a rel="external" href="https://stenciljs.com/docs/stencil-store" target="_blank">Stencil Store</a>{', and '}
|
||||
<a rel="external" href="https://supabase.io" target="_blank">Supabase</a>.</p>
|
||||
<br />
|
||||
|
||||
<p>
|
||||
Created by: <a href="https://github.com/ftonato" rel="author" target="_blank">Ademílson F. Tonato</a> - <a href="https://twitter.com/ftonato" rel="author" target="_blank">@ftonato</a>.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
describe('app-root', () => {
|
||||
it('renders', async () => {
|
||||
const page = await newE2EPage({ url: '/' });
|
||||
|
||||
const element = await page.find('app-root');
|
||||
expect(element).toHaveClass('hydrated');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
.type-a {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
align-items: center;
|
||||
background-color: $clr-bg-box;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.025);
|
||||
display: flex;
|
||||
height: 75px;
|
||||
margin-bottom: 2rem;
|
||||
overflow: hidden;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.username {
|
||||
color: $clr-text-headline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Component for showing application content.
|
||||
* It is the main entry point of the application
|
||||
*
|
||||
* @component AppRoot
|
||||
*/
|
||||
|
||||
import { Component, h, Prop } from '@stencil/core';
|
||||
import { RouterHistory } from '@stencil/router';
|
||||
import { AppState } from "../../store/app.store";
|
||||
import { AuthService } from "../../services/auth.service";
|
||||
import { appConfig } from '../../config/config';
|
||||
|
||||
/**
|
||||
* Handle private routes
|
||||
* Routes that can't be accessed until user logs in
|
||||
* Functional component
|
||||
* @param {string} component name
|
||||
* @param {object} props name
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
const PrivateRoute = ({ component, ...props }: { [key: string]: any }) => {
|
||||
const Component = component;
|
||||
|
||||
return (
|
||||
<stencil-route {...props} routeRender={
|
||||
(props: { [key: string]: any }) => {
|
||||
if (AppState.isAuthenticated) {
|
||||
return <Component {...props} {...props.componentProps}></Component>;
|
||||
}
|
||||
return <stencil-router-redirect url="/login"></stencil-router-redirect>
|
||||
}
|
||||
} />
|
||||
);
|
||||
}
|
||||
|
||||
@Component({
|
||||
tag: 'app-root',
|
||||
styleUrl: 'app-root.scss',
|
||||
|
||||
})
|
||||
export class AppRoot {
|
||||
/**
|
||||
* AuthService instance.
|
||||
* @name authService
|
||||
* @type {Object}
|
||||
*/
|
||||
private authService = AuthService;
|
||||
|
||||
/**
|
||||
* history instance.
|
||||
*/
|
||||
@Prop() history: RouterHistory;
|
||||
|
||||
/**
|
||||
* handle logout
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
async logout() {
|
||||
await this.authService.logOut();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render component <app-root>
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
render() {
|
||||
if (AppState.appInit === true) {
|
||||
return (<div>Loading...</div>);
|
||||
}
|
||||
if (AppState.appInitError != '') {
|
||||
return (<app-flash-message closable={false}>{AppState.appInitError}</app-flash-message>);
|
||||
}
|
||||
const titleSuffix = ` | ${appConfig.pageTitlePrefix}`
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
AppState.isAuthenticated &&
|
||||
<header>
|
||||
{
|
||||
AppState.user
|
||||
&& (
|
||||
<div>
|
||||
<span>
|
||||
Welcome <span class="username">{' ' + AppState.user.email + ' '}</span>
|
||||
</span>
|
||||
<a href="#" onClick={() => this.logout()}>
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</header>
|
||||
}
|
||||
<main>
|
||||
<stencil-router titleSuffix={titleSuffix}>
|
||||
<stencil-route-switch scrollTopOffset={0}>
|
||||
<PrivateRoute url="/" component="app-home" exact={true} />
|
||||
<stencil-route url='/login' component='app-auth-login' />
|
||||
<stencil-route url='/register' component='app-auth-register' />
|
||||
<stencil-route component="app-page-home" />
|
||||
</stencil-route-switch>
|
||||
</stencil-router>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
describe('app-auth-login', () => {
|
||||
it('renders', async () => {
|
||||
const page = await newE2EPage();
|
||||
await page.setContent('<app-auth-login></app-auth-login>');
|
||||
|
||||
const element = await page.find('app-auth-login');
|
||||
expect(element).toHaveClass('hydrated');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Component for showing user login.
|
||||
*
|
||||
* @component AppLogin
|
||||
*/
|
||||
|
||||
import { Component, h, Prop, State } from '@stencil/core';
|
||||
import { RouterHistory } from '@stencil/router';
|
||||
import { AppState } from "../../../store/app.store";
|
||||
import { AuthService } from "../../../services/auth.service";
|
||||
import { httpCode, appConfig } from '../../../config/config';
|
||||
import { getFormValidations } from '../../../util/util';
|
||||
|
||||
@Component({
|
||||
tag: 'app-auth-login',
|
||||
styleUrl: '../shared/app-auth-shared.scss',
|
||||
})
|
||||
export class AppLogin {
|
||||
|
||||
/**
|
||||
* AuthService instance.
|
||||
* @name authService
|
||||
* @type {Object}
|
||||
*/
|
||||
private authService = AuthService;
|
||||
|
||||
/**
|
||||
* history instance.
|
||||
*/
|
||||
@Prop() history: RouterHistory;
|
||||
|
||||
/**
|
||||
* Register form inputs and validations
|
||||
* @name formControls
|
||||
* @type {Object}
|
||||
*/
|
||||
@State() formControls = getFormValidations('login');
|
||||
|
||||
/**
|
||||
* Form submission state
|
||||
* @name submitted
|
||||
* @type {boolean}
|
||||
*/
|
||||
@State() submitted = false;
|
||||
|
||||
/**
|
||||
* Form loading state
|
||||
* @name formLoader
|
||||
* @type {boolean}
|
||||
*/
|
||||
@State() formLoader = false;
|
||||
|
||||
/**
|
||||
* Form error state
|
||||
* @name loginError
|
||||
* @type {string}
|
||||
*/
|
||||
@State() loginError: string = "";
|
||||
|
||||
/**
|
||||
* handle form values and do the validation work
|
||||
* @param {string} controlName Control name for register form
|
||||
* @param {any} value Control value for register form
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
changeFormValue(controlName, value) {
|
||||
this.formControls = {
|
||||
...this.formControls,
|
||||
[controlName]: {
|
||||
...this.formControls[controlName],
|
||||
value: value,
|
||||
isValid: this.formControls[controlName].validate(value)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle login form
|
||||
* Check user in supabase
|
||||
* Redirect user to dashboard
|
||||
* @param {event} e Form submit event
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
async handleLogin(e) {
|
||||
e.preventDefault();
|
||||
this.submitted = true;
|
||||
let isFormValid = true;
|
||||
//Run all validation functions
|
||||
for (let controlName in this.formControls) {
|
||||
let control = this.formControls[controlName];
|
||||
control.validate(control.value);
|
||||
if (!control.isValid) {
|
||||
isFormValid = false;
|
||||
}
|
||||
}
|
||||
if (isFormValid) {
|
||||
this.formLoader = true;
|
||||
this.loginError = '';
|
||||
const loginRes = await this.authService.login({ email: this.formControls.email.value, password: this.formControls.password.value });
|
||||
if (loginRes.status == httpCode.success) {
|
||||
this.formLoader = false;
|
||||
this.history.push(`/`, {});
|
||||
} else {
|
||||
this.formLoader = false;
|
||||
this.loginError = loginRes.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render component <app-auth-login>
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
render() {
|
||||
if (AppState.isAuthenticated) {
|
||||
return <stencil-router-redirect url="/" />
|
||||
}
|
||||
return (
|
||||
<section class="app-section">
|
||||
<stencil-route-title pageTitle={appConfig.pageTitle.login} />
|
||||
|
||||
<div class="auth-box">
|
||||
<div class="auth-box-formbox">
|
||||
<div class="auth-box-headline">
|
||||
Don't have an account? <a href="/register">Register</a>
|
||||
</div>
|
||||
<div class="auth-box-login">
|
||||
<h1>Welcome to <span class="stencil">Stencil</span> & <span class="supabase">Supabase</span> starter App</h1>
|
||||
<p>
|
||||
Insert your auth data to navigate through the app
|
||||
</p>
|
||||
<form onSubmit={e => this.handleLogin(e)} novalidate>
|
||||
<div>
|
||||
<label htmlFor="email"> E-Mail</label>
|
||||
<input type="email" name="email" class="input-email" autocomplete="off"
|
||||
value={this.formControls.email.value}
|
||||
onInput={(ev: any) => this.changeFormValue("email", ev.target.value)} />
|
||||
|
||||
{
|
||||
(!this.formControls.email.isValid &&
|
||||
this.submitted === true) &&
|
||||
<span class="text-danger">Please enter your email</span>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password"> Password</label>
|
||||
<input type="password" name="password" class="input-password"
|
||||
value={this.formControls.password.value}
|
||||
onInput={(ev: any) => this.changeFormValue("password", ev.target.value)}
|
||||
/>
|
||||
|
||||
{
|
||||
(!this.formControls.password.isValid &&
|
||||
this.submitted === true) &&
|
||||
<span class="text-danger">Please enter your password</span>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn" disabled={this.formLoader}>{this.formLoader ? appConfig.loadingBtnTxt : 'Login to account'}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{this.loginError && <app-flash-message>{this.loginError}</app-flash-message>}
|
||||
|
||||
<div class="alternate-text">Or sign in with</div>
|
||||
<div class="alternate-boxes">
|
||||
<div class="alternate-box">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon icon-tabler icon-tabler-brand-google"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M17.788 5.108a9 9 0 1 0 3.212 6.892h-8"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="alternate-box">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon icon-tabler icon-tabler-brand-facebook"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M7 10v4h3v7h4v-7h3l1 -4h-4v-2a1 1 0 0 1 1 -1h3v-4h-3a5 5 0 0 0 -5 5v2h-3"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="alternate-box">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon icon-tabler icon-tabler-brand-apple"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M9 7c-3 0 -4 3 -4 5.5c0 3 2 7.5 4 7.5c1.088 -.046 1.679 -.5 3 -.5c1.312 0 1.5 .5 3 .5s4 -3 4 -5c-.028 -.01 -2.472 -.403 -2.5 -3c-.019 -2.17 2.416 -2.954 2.5 -3c-1.023 -1.492 -2.951 -1.963 -3.5 -2c-1.433 -.111 -2.83 1 -3.5 1c-.68 0 -1.9 -1 -3 -1z"
|
||||
></path>
|
||||
<path d="M12 4a2 2 0 0 0 2 -2a2 2 0 0 0 -2 2"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
describe('app-auth-register', () => {
|
||||
it('renders', async () => {
|
||||
const page = await newE2EPage();
|
||||
await page.setContent('<app-auth-register></app-auth-register>');
|
||||
|
||||
const element = await page.find('app-auth-register');
|
||||
expect(element).toHaveClass('hydrated');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Component for showing user register.
|
||||
*
|
||||
* @component AppAuthRegister
|
||||
*/
|
||||
|
||||
import { Component, State, h, Prop } from '@stencil/core';
|
||||
import { RouterHistory } from '@stencil/router';
|
||||
import { AuthService } from "../../../services/auth.service";
|
||||
import { AppState } from '../../../store/app.store';
|
||||
import { httpCode, appConfig } from '../../../config/config';
|
||||
import { getFormValidations } from '../../../util/util';
|
||||
|
||||
@Component({
|
||||
tag: 'app-auth-register',
|
||||
styleUrl: '../shared/app-auth-shared.scss',
|
||||
})
|
||||
export class AppAuthRegister {
|
||||
|
||||
/**
|
||||
* AuthService instance.
|
||||
* @name authService
|
||||
* @type {Object}
|
||||
*/
|
||||
private authService = AuthService;
|
||||
|
||||
/**
|
||||
* history instance.
|
||||
*/
|
||||
@Prop() history: RouterHistory;
|
||||
|
||||
/**
|
||||
* Register form inputs and validations
|
||||
*/
|
||||
@State() formControls = getFormValidations('register');
|
||||
|
||||
/**
|
||||
* Form submission state
|
||||
*/
|
||||
@State() submitted = false;
|
||||
|
||||
/**
|
||||
* Form loading state
|
||||
*/
|
||||
@State() formLoader = false;
|
||||
|
||||
/**
|
||||
* Form error state
|
||||
*/
|
||||
@State() registerError: string = '';
|
||||
|
||||
|
||||
/**
|
||||
* handle form values and do the validation work
|
||||
* @param {string} controlName Control name for register form
|
||||
* @param {any} value Control value for register form
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
changeFormValue(controlName, value) {
|
||||
this.formControls = {
|
||||
...this.formControls,
|
||||
[controlName]: {
|
||||
...this.formControls[controlName],
|
||||
value: value,
|
||||
isValid: this.formControls[controlName].validate(value)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle register form
|
||||
* Save user in supabase and update username
|
||||
* Redirect user to dashboard
|
||||
* @param {event} e Form submit event
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
async handleRegister(e) {
|
||||
e.preventDefault();
|
||||
this.submitted = true;
|
||||
let isFormValid = true;
|
||||
//Run all validation functions
|
||||
for (let controlName in this.formControls) {
|
||||
let control = this.formControls[controlName];
|
||||
control.validate(control.value);
|
||||
if (!control.isValid) {
|
||||
isFormValid = false;
|
||||
}
|
||||
}
|
||||
if (isFormValid) {
|
||||
this.formLoader = true
|
||||
this.registerError = '';
|
||||
const registerRes = await this.authService.register({ email: this.formControls.email.value, password: this.formControls.password.value });
|
||||
if (registerRes.status == httpCode.success) {
|
||||
await this.authService.updateProfile(this.formControls.email.value);
|
||||
this.history.push(`/`, {});
|
||||
} else {
|
||||
this.registerError = registerRes.message;
|
||||
this.formLoader = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render component <app-auth-register>
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
render() {
|
||||
if (AppState.isAuthenticated) {
|
||||
return <stencil-router-redirect url="/" />
|
||||
}
|
||||
return (
|
||||
<section class="app-section">
|
||||
<stencil-route-title pageTitle={appConfig.pageTitle.register} />
|
||||
|
||||
<div class="auth-box">
|
||||
<div class="auth-box-formbox">
|
||||
<div class="auth-box-headline">
|
||||
Do you have an account? <a href="/login">Login</a>
|
||||
</div>
|
||||
<div class="auth-box-register">
|
||||
<h1>Welcome to <span class="stencil">Stencil</span> & <span class="supabase">Supabase</span> starter App</h1>
|
||||
<p>
|
||||
Insert your profile data to be able to navigate through the app
|
||||
</p>
|
||||
<form onSubmit={e => this.handleRegister(e)} novalidate>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email"> E-Mail</label>
|
||||
<input type="email" name="email" class="input-email" autocomplete="off"
|
||||
value={this.formControls.email.value}
|
||||
onInput={(ev: any) => this.changeFormValue("email", ev.target.value)} />
|
||||
|
||||
{
|
||||
(!this.formControls.email.isValid &&
|
||||
this.submitted === true) &&
|
||||
<span class="text-danger">Please enter your email</span>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password"> Password</label>
|
||||
<input type="password" name="password" class="input-password"
|
||||
value={this.formControls.password.value}
|
||||
onInput={(ev: any) => this.changeFormValue("password", ev.target.value)}
|
||||
/>
|
||||
|
||||
{
|
||||
(!this.formControls.password.isValid &&
|
||||
this.submitted === true) &&
|
||||
<span class="text-danger">Please enter your password</span>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn" disabled={this.formLoader}>{this.formLoader ? appConfig.loadingBtnTxt : 'Register now'}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{this.registerError && <app-flash-message>{this.registerError}</app-flash-message>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
|
||||
.auth-box {
|
||||
background-color: $clr-bg-box;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.025);
|
||||
display: flex;
|
||||
height: 60vh;
|
||||
min-height: 50em;
|
||||
overflow: hidden;
|
||||
width: 75em;
|
||||
}
|
||||
|
||||
.auth-box-formbox {
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 4em 6em;
|
||||
}
|
||||
|
||||
.auth-box-login,
|
||||
.auth-box-register {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-wrap: wrap;
|
||||
|
||||
h1 > span {
|
||||
color: $clr-bg-quote;
|
||||
}
|
||||
|
||||
& > * {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
& > div {
|
||||
padding: 1.5em 0;
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
border: 1px solid $clr-text-main;
|
||||
border-radius: 5px;
|
||||
color: $clr-text-main;
|
||||
padding: 1.5em 2em;
|
||||
width: 100%;
|
||||
|
||||
&.input-email,
|
||||
&.input-password {
|
||||
background-position: right 1em top 1.25em;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
&.input-email {
|
||||
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="lightgrey" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="4"></circle><path d="M16 12v1.5a2.5 2.5 0 0 0 5 0v-1.5a9 9 0 1 0 -5.5 8.28"></path></svg>');
|
||||
}
|
||||
|
||||
&.input-password {
|
||||
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="lightgrey" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><rect x="5" y="11" width="14" height="10" rx="2"></rect><circle cx="12" cy="16" r="1"></circle><path d="M8 11v-4a4 4 0 0 1 8 0v4"></path></svg>');
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: $clr-bg-quote;
|
||||
outline-color: $clr-bg-quote;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
border: 1px solid $clr-text-main;
|
||||
border-radius: 5px;
|
||||
color: $clr-text-main;
|
||||
padding: 1.5em 2em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label {
|
||||
background: $clr-bg-box;
|
||||
font-size: 0.85em;
|
||||
left: 1.5em;
|
||||
padding: 0 0.5em;
|
||||
position: absolute;
|
||||
top: 1.25em;
|
||||
}
|
||||
|
||||
span.text-danger {
|
||||
color: #f97130;
|
||||
text-align: left;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
&:focus-within label {
|
||||
color: $clr-bg-quote;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: $clr-bg-quote;
|
||||
border-color: $clr-bg-quote;
|
||||
color: $clr-bg-box;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1;
|
||||
transition: all 500ms ease;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: $clr-bg-quote-hover;
|
||||
border-color: $clr-bg-quote-hover;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alternate-text {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin: 1.5em 0;
|
||||
text-align: center;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
border-bottom: 1px solid $clr-text-main;
|
||||
content: '';
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
&::after {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.alternate-boxes {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
justify-content: space-between;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.alternate-box {
|
||||
border: 1px solid rgba($clr-text-main, 0.5);
|
||||
border-radius: 5px;
|
||||
color: $clr-text-headline;
|
||||
cursor: pointer;
|
||||
padding: 1em 1em;
|
||||
text-align: center;
|
||||
transition: all 500ms ease;
|
||||
width: 100%;
|
||||
|
||||
svg {
|
||||
transition: transform 500ms ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($clr-text-main, 0.25);
|
||||
border-color: $clr-text-main;
|
||||
|
||||
svg {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
describe('app-page-home', () => {
|
||||
it('renders', async () => {
|
||||
const page = await newE2EPage();
|
||||
|
||||
await page.setContent('<app-page-home></app-page-home>');
|
||||
const element = await page.find('app-page-home');
|
||||
expect(element).toHaveClass('hydrated');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
.wrapper_404 {
|
||||
flex-grow: 2;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
width: 40vw;
|
||||
|
||||
|
||||
h1 {
|
||||
color: $clr-bg-quote;
|
||||
font-size: 5em;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.5em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
justify-content: flex-end;
|
||||
letter-spacing: .2em;
|
||||
overflow: hidden;
|
||||
padding-top: 20px;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
transition: .3s;
|
||||
|
||||
.buttons {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Component, h } from '@stencil/core';
|
||||
|
||||
@Component({
|
||||
tag: 'app-page-home',
|
||||
styleUrl: 'app-page-home.scss',
|
||||
|
||||
})
|
||||
export class AppPageHome {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<section class="wrapper_404 home-box">
|
||||
<div>
|
||||
<h1>Page not found ~ 404</h1>
|
||||
<p>The page you are requested is not found.</p>
|
||||
<stencil-route-link url='/'>
|
||||
<div class="buttons">Back to Home</div>
|
||||
</stencil-route-link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
const supabaseConfig = {
|
||||
supabaseUrl: 'https://akakxshwrukpetyzjnyi.supabase.co',
|
||||
supabaseKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMDk3ODA4OSwiZXhwIjoxOTI2NTU0MDg5fQ.xQTiGKgZ0p7Ch1paDP2KCExiVhFeqZicLm4s36XFUIk'
|
||||
};
|
||||
|
||||
const httpCode = {
|
||||
success: 200,
|
||||
notFound: 404,
|
||||
serverError: 504
|
||||
};
|
||||
|
||||
const appName: string = 'Stencil & Supabase';
|
||||
|
||||
const appConfig = {
|
||||
name: appName,
|
||||
pageTitlePrefix: appName,
|
||||
pageTitle: {
|
||||
login: 'Login',
|
||||
register: 'Register',
|
||||
dashboard: 'Dashboard',
|
||||
password: 'Update Password'
|
||||
},
|
||||
loadingBtnTxt: 'Loading...'
|
||||
};
|
||||
|
||||
export {
|
||||
supabaseConfig,
|
||||
httpCode,
|
||||
appConfig
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
@import '../assets/scss/definitions';
|
||||
@@ -0,0 +1,7 @@
|
||||
export default async () => {
|
||||
/**
|
||||
* The code to be executed should be placed within a default function that is
|
||||
* exported by the global script. Ensure all of the code in the global script
|
||||
* is wrapped in the function() that is exported.
|
||||
*/
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>Stencil & Supabase</title>
|
||||
|
||||
<meta name="Description"
|
||||
content="Welcome to the Stencil & Supabase starter App. You can use this starter to build entire apps all with web components using Stencil & Supabase">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
|
||||
<meta name="theme-color" content="#16161d">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta http-equiv="x-ua-compatible" content="IE=Edge">
|
||||
|
||||
<script type="module" src="/build/app.esm.js"></script>
|
||||
<script nomodule src="/build/app.js"></script>
|
||||
<link href="/build/app.css" rel="stylesheet">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/assets/icon/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/assets/icon/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/assets/icon/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/assets/icon/apple-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/assets/icon/apple-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/assets/icon/apple-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/assets/icon/apple-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/assets/icon/apple-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icon/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/assets/icon/android-icon-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/assets/icon/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icon/favicon-16x16.png">
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="/assets/icon/ms-icon-144x144.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Stencil & Supabase -->
|
||||
<app-root></app-root>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { Components, JSX } from './components';
|
||||
import '@stencil/router';
|
||||
@@ -0,0 +1,29 @@
|
||||
interface ICM {
|
||||
[key: string]: any
|
||||
}
|
||||
interface IBaseRes {
|
||||
status: number;
|
||||
data: any,
|
||||
message: string;
|
||||
errors?: ICM
|
||||
}
|
||||
interface IUserBase {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
interface IUserRegister extends IUserBase {
|
||||
name?: string
|
||||
}
|
||||
interface IUserLogin extends IUserBase {
|
||||
stayLoggedIn?: boolean
|
||||
}
|
||||
interface IErrorRes {
|
||||
code: string;
|
||||
message: any
|
||||
}
|
||||
export {
|
||||
IBaseRes,
|
||||
IUserRegister,
|
||||
IUserLogin,
|
||||
IErrorRes
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "Stencil Supabase Starter",
|
||||
"short_name": "Stencil Supabase",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"icons": [{
|
||||
"src": "assets/icon/icon.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}],
|
||||
"background_color": "#16161d",
|
||||
"theme_color": "#16161d"
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* This class act as service to handle supabase operations related to user authentications.
|
||||
* Login/Register/Change Password/Logout
|
||||
*
|
||||
* @class SupabaseAuthService
|
||||
*
|
||||
*/
|
||||
|
||||
import SupabaseService from './supabase.service'
|
||||
import { supabaseConfig, httpCode } from "../config/config";
|
||||
import { AppState, setStateProp } from "../store/app.store";
|
||||
import { IBaseRes, IUserRegister, IUserLogin } from "../interface/interface";
|
||||
import { appMsg } from "../util/util";
|
||||
|
||||
class SupabaseAuthService {
|
||||
supabase: any;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
const connection = new SupabaseService(supabaseConfig.supabaseUrl, supabaseConfig.supabaseKey);
|
||||
this.supabase = connection.supabase;
|
||||
this.init()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize supabase instance
|
||||
* If user is already login set user state
|
||||
* @name init
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
private init(): void {
|
||||
setStateProp('appInit', true)
|
||||
|
||||
try {
|
||||
this.supabase.auth.onAuthStateChange((_event, session) => {
|
||||
|
||||
if (session) {
|
||||
AppState.user = session.user;
|
||||
AppState.isAuthenticated = true;
|
||||
} else {
|
||||
AppState.user = undefined;
|
||||
AppState.isAuthenticated = false;
|
||||
}
|
||||
})
|
||||
|
||||
AppState.appInit = false;
|
||||
|
||||
} catch (error) {
|
||||
const errorRes = this.buildErrorRes(httpCode.serverError, error);
|
||||
AppState.appInit = false;
|
||||
AppState.appInitError = errorRes.message ? errorRes.message : appMsg('server-ref');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build success response
|
||||
* @name buildRes
|
||||
*
|
||||
* @param {number} status status code
|
||||
* @param {any} data response data
|
||||
* @param {string} message message to show
|
||||
* @param {any} errors errors object if any
|
||||
*
|
||||
* @returns {IBaseRes}
|
||||
*/
|
||||
private buildRes(status: number, data?: any, message?: string, errors?: any): IBaseRes {
|
||||
return {
|
||||
status,
|
||||
data,
|
||||
errors,
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build error response
|
||||
* @name buildErrorRes
|
||||
*
|
||||
* @param {number} status status code
|
||||
* @param {any} errors errors object if any
|
||||
*
|
||||
* @returns {IBaseRes}
|
||||
*/
|
||||
private buildErrorRes(status: number, errors?: any): IBaseRes {
|
||||
return {
|
||||
status: status,
|
||||
data: null,
|
||||
errors: {},
|
||||
message: errors.error_description ? errors.error_description : errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle supabase login
|
||||
* @name login
|
||||
*
|
||||
* @param {IUserLogin} userData
|
||||
*
|
||||
* @returns {IBaseRes} Promise that represents <IBaseRes> object
|
||||
*/
|
||||
async login({email, password}: IUserLogin): Promise<IBaseRes> {
|
||||
let _error = null
|
||||
try {
|
||||
const { error, data } = await this.supabase.auth.signIn({ email, password});
|
||||
|
||||
if (error) {
|
||||
_error = error.message;
|
||||
return this.buildErrorRes(httpCode.serverError, error.message);
|
||||
}
|
||||
|
||||
return this.buildRes(httpCode.success, data);
|
||||
} catch (er) {
|
||||
alert('x')
|
||||
return this.buildErrorRes(httpCode.serverError, _error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle supabase registration
|
||||
* @name register
|
||||
*
|
||||
* @param {IUserRegister} userData
|
||||
*
|
||||
*
|
||||
*/
|
||||
// @returns {IBaseRes} Promise that represents <IBaseRes> object
|
||||
async register(userData: IUserRegister) /* Promise<IBaseRes> */ {
|
||||
try {
|
||||
const { error, data } = await this.supabase.auth.signUp({ email: userData.email, password: userData.password });
|
||||
if (error) {
|
||||
return this.buildErrorRes(httpCode.serverError, error.message);
|
||||
}
|
||||
|
||||
return this.buildRes(httpCode.success, data);
|
||||
} catch (error) {
|
||||
return this.buildErrorRes(httpCode.serverError, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle supabase logout
|
||||
* @name logOut
|
||||
*
|
||||
* @returns {IBaseRes} Promise that represents <IBaseRes> object
|
||||
*/
|
||||
async logOut(): Promise<IBaseRes> {
|
||||
try {
|
||||
if (AppState.isAuthenticated) {
|
||||
AppState.isAuthenticated = false;
|
||||
AppState.user = undefined;
|
||||
try {
|
||||
await this.supabase.auth.signOut();
|
||||
} catch (error) {
|
||||
return this.buildErrorRes(httpCode.serverError, error);
|
||||
}
|
||||
}
|
||||
return this.buildRes(httpCode.success, {}, appMsg('logout'));
|
||||
} catch (error) {
|
||||
return this.buildErrorRes(httpCode.serverError, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle supabase update user profile
|
||||
* @name updateProfile
|
||||
*
|
||||
* @param {string} email
|
||||
*
|
||||
* @returns {IBaseRes} Promise that represents <IBaseRes> object
|
||||
*/
|
||||
async updateProfile(email: string): Promise<IBaseRes> {
|
||||
try {
|
||||
AppState.user = {
|
||||
...AppState.user,
|
||||
email,
|
||||
};
|
||||
AppState.isAuthenticated = true
|
||||
|
||||
return this.buildRes(httpCode.success, {}, appMsg('update-profile'));
|
||||
} catch (error) {
|
||||
return this.buildErrorRes(httpCode.serverError, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const AuthService = new SupabaseAuthService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
let instance = null
|
||||
|
||||
/**
|
||||
* This class is a singleton service to handle Supabase connection.
|
||||
*
|
||||
* @class SupabaseService
|
||||
* @docs https://gist.github.com/ftonato/23585d6098490d0239feaeddb7f1e56c
|
||||
* @author Ademílson F. Tonato - ftonato
|
||||
*
|
||||
*/
|
||||
export default class SupabaseService {
|
||||
supabase: any
|
||||
constructor(supabaseUrl: string, supabaseKey: string) {
|
||||
|
||||
if (!supabaseUrl) throw new Error(`${SupabaseService.getClassName()} => supabaseUrl is required.`)
|
||||
if (!supabaseKey) throw new Error(`${SupabaseService.getClassName()} => supabaseKey is required.`)
|
||||
|
||||
if (!instance) {
|
||||
instance = this
|
||||
}
|
||||
|
||||
this.supabase = createClient(supabaseUrl, supabaseKey)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
static getClassName(): string {
|
||||
return SupabaseService.name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createStore } from '@stencil/store';
|
||||
|
||||
const store = createStore({
|
||||
user: undefined,
|
||||
isAuthenticated: false,
|
||||
appInitError: '',
|
||||
appInit: false,
|
||||
registerLoader: false,
|
||||
loginLoader: false
|
||||
});
|
||||
|
||||
store.onChange('isAuthenticated', (value) => {
|
||||
AppState.isAuthenticated = value;
|
||||
});
|
||||
store.onChange('user', (value) => {
|
||||
AppState.user = value;
|
||||
});
|
||||
store.onChange('appInitError', (value) => {
|
||||
AppState.appInitError = value;
|
||||
});
|
||||
store.onChange('appInit', (value) => {
|
||||
AppState.appInit = value;
|
||||
});
|
||||
|
||||
export const AppState = store.state;
|
||||
export const getStateProp = store.get;
|
||||
export const setStateProp = store.set;
|
||||
export const resetAppState = store.reset;
|
||||
@@ -0,0 +1,46 @@
|
||||
const appMsg = (key: string): string => {
|
||||
const msg = {
|
||||
'server': 'An internal error occurred, please try again.',
|
||||
'server-ref': 'Internal error, Please try to refresh your page.',
|
||||
'not-found': 'The resource you are looking for not found.',
|
||||
'logout': 'You are logout out successfully.',
|
||||
};
|
||||
return msg[key] ? msg[key] : '';
|
||||
};
|
||||
const checkEmpty = (value: any): boolean => {
|
||||
return value;
|
||||
}
|
||||
const getFormValidations = (key, values = {}) => {
|
||||
const validations = {
|
||||
login: {
|
||||
email: {
|
||||
value: values['email'] ? values['email'] : null,
|
||||
validate: checkEmpty,
|
||||
isValid: values['email'] ? true : false
|
||||
},
|
||||
password: {
|
||||
value: values['password'] ? values['password'] : null,
|
||||
validate: checkEmpty,
|
||||
isValid: values['password'] ? true : false
|
||||
}
|
||||
},
|
||||
register: {
|
||||
email: {
|
||||
value: values['email'] ? values['email'] : null,
|
||||
validate: checkEmpty,
|
||||
isValid: values['email'] ? true : false
|
||||
},
|
||||
password: {
|
||||
value: values['password'] ? values['password'] : null,
|
||||
validate: checkEmpty,
|
||||
isValid: values['password'] ? true : false
|
||||
}
|
||||
},
|
||||
};
|
||||
return validations[key] ? validations[key] : {}
|
||||
}
|
||||
|
||||
export {
|
||||
appMsg,
|
||||
getFormValidations
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Config } from '@stencil/core';
|
||||
import { sass } from '@stencil/sass';
|
||||
|
||||
export const config: Config = {
|
||||
globalStyle: 'src/global/app.scss',
|
||||
globalScript: 'src/global/app.ts',
|
||||
taskQueue: 'async',
|
||||
enableCache: false,
|
||||
outputTargets: [
|
||||
{
|
||||
type: 'www',
|
||||
// Comment the following line to disable service workers in production
|
||||
serviceWorker: null,
|
||||
baseUrl: 'https://stencil-supabase.local/',
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
sass(
|
||||
{injectGlobalPaths: [
|
||||
'src/assets/scss/helpers/variables.scss'
|
||||
]}
|
||||
)
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowUnreachableCode": false,
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"moduleResolution": "node",
|
||||
"module": "esnext",
|
||||
"target": "es2017",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "h"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user