chore: add stencil-supabase to example/stencil-supabase

This commit is contained in:
Ademílson F. Tonato committed 2021-02-27 12:46:19 +00:00
1 parent b06114ac5c
commit ae9a0bfa50
45 files changed
+9807

No files matched your search

+15
View File
@@ -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
+39
View File
@@ -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"
}
}
+201
View File
@@ -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
+13
View File
@@ -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
}
+21
View File
@@ -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.
+78
View File
@@ -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 &copy;](LICENSE)
+51
View File
@@ -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

File diff suppressed because it is too large. Load diff
+43
View File
@@ -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
View File
@@ -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()}>&times;</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';
+7
View File
@@ -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.
*/
};
+45
View File
@@ -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>
+2
View File
@@ -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
}
+13
View File
@@ -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;
+46
View File
@@ -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
}
+24
View File
@@ -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'
]}
)
]
};
+25
View File
@@ -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"
]
}