Skip to content

Commit

Permalink
feat(picker): allow headless use of this lib
Browse files Browse the repository at this point in the history
This change will mean developers can now use the firebase service directly to render their own UI.
Only thing devs will have to do is the following

- Set hide-customer-picker="true" on eva-config-picker, this will hide the picker if you're logged in / after login
- Listen for authStateChange before trying to fetch any data from the firebase service
- Fetch data from firebase and render their own UI
  • Loading branch information
realappie committed Nov 9, 2021
1 parent 834a2fb commit a232e4f
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 90 deletions.
15 changes: 14 additions & 1 deletion src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { BaseEnvironment } from "./components/picker-customers/picker-customers";
import { AuthState } from "./components/picker/auth-state.enum";
import { BaseEnvironment } from "./typings";
export namespace Components {
interface EvaConfigPicker {
/**
* This will allow consumers to fetch the data themselves using the build in firebase service, ensuring they can match the look & feel of their application.
*/
"hideCustomerPicker": boolean;
}
interface EvaConfigPickerCustomer {
}
Expand Down Expand Up @@ -72,6 +77,14 @@ declare global {
}
declare namespace LocalJSX {
interface EvaConfigPicker {
/**
* This will allow consumers to fetch the data themselves using the build in firebase service, ensuring they can match the look & feel of their application.
*/
"hideCustomerPicker"?: boolean;
/**
* will emit whenever the auth state changes
*/
"onAuthStateChange"?: (event: CustomEvent<AuthState>) => void;
}
interface EvaConfigPickerCustomer {
/**
Expand Down
78 changes: 12 additions & 66 deletions src/components/picker-customers/picker-customers.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,8 @@
import { Component, Event, EventEmitter, h, State } from '@stencil/core';
import { signOut } from "firebase/auth";
import { collection, DocumentReference, getDoc, getDocs } from 'firebase/firestore';
import { BaseEnvironment, ExtendedViewCustomer, SummarisedViewCustomer } from '../../typings';
import { firebaseServiceInstance } from '../../firebase';
interface BaseCustomer {
logoPath: string;
name: string;
}

interface ViewCustomerSmall extends BaseCustomer {
baseEnvironments: DocumentReference[];
}

export interface BaseEnvironment {
endpoint: string;
type: string;
customerName: string;
}

interface ViewCustomerBig extends BaseCustomer {
baseEnvironments: BaseEnvironment[];
}

enum CustomerDataState {
LOADED,
Expand All @@ -42,10 +25,12 @@ export class Customers {
endPointSelect: EventEmitter<BaseEnvironment>;

@State()
viewCustomers: ViewCustomerSmall[] = [];
viewCustomersSummary: SummarisedViewCustomer[] = [];

@State()
selectedCustomer: ViewCustomerBig;
@State()/**
* will contain all the information in the summary, fetched
*/
selectedCustomer: ExtendedViewCustomer;

@State()
customerDataState: CustomerDataState = CustomerDataState.LOADING;
Expand All @@ -59,36 +44,10 @@ export class Customers {

async componentWillLoad() {
try {
const customersSnapshot = await getDocs(collection(this.firestore, 'customer'));

const customerDocuments = customersSnapshot.docs;

const viewCustomers = [];

for (const customerDocument of customerDocuments) {
try {
const customerData = customerDocument.data();
this.viewCustomersSummary = await firebaseServiceInstance.getViewCustomersSummary();

// We don't want the generic customer in the selection UI
//
if (customerData.name !== 'generic') {
const viewCustomer: ViewCustomerSmall = {
name: customerData.name,
logoPath: customerData.logoPath,
baseEnvironments: customerData.baseEnvironments
}

viewCustomers.push(viewCustomer);
}

this.customerDataState = CustomerDataState.LOADED
} catch (error) {
console.error('[eva-config-picker-customer] error getting customer data for', customerDocument);
}

}

this.viewCustomers = viewCustomers.sort((a, b) => a.name.localeCompare(b.name));
this.customerDataState = CustomerDataState.LOADED;
} catch ( error ) {
if ( error.code === 'permission-denied' ) {
this.customerDataState = CustomerDataState.UNAUTHORIZED;
Expand All @@ -100,21 +59,8 @@ export class Customers {
}
}

async selectCustomer(customer: ViewCustomerSmall) {
const baseEnvironments = [];

for (const baseEnvironment of customer.baseEnvironments ) {
const baseEnvironmentDocumentData = await getDoc(baseEnvironment);

const baseEnvironmentData = baseEnvironmentDocumentData.data();

baseEnvironments.push(baseEnvironmentData);
}

this.selectedCustomer = {
...customer,
baseEnvironments
}
async selectCustomer(customer: SummarisedViewCustomer) {
this.selectedCustomer = await firebaseServiceInstance.getExtendedViewCustomer(customer);
}

render() {
Expand Down Expand Up @@ -164,7 +110,7 @@ export class Customers {
private renderCustomers() {
return (
<div class="grid-container">
{ this.viewCustomers.map( customer => this.renderCustomerCard(customer)) }
{ this.viewCustomersSummary.map( customer => this.renderCustomerCard(customer)) }
</div>
)
}
Expand Down Expand Up @@ -205,7 +151,7 @@ export class Customers {

private async logout() {
try {
await signOut(this.auth);
await firebaseServiceInstance.signOut();
} catch ( error ) {
console.error('[eva-config-picker-customer] error logging out', error);
}
Expand Down
8 changes: 8 additions & 0 deletions src/components/picker/auth-state.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Ideally this file is placed in typings.d.ts but thats not possible
// https://stackoverflow.com/questions/52351620/stenciljs-typescript-cannot-find-name-when-exporting-an-enum
//
export enum AuthState {
LOADING = 'loading',
LOGGED_IN = 'logged_in',
LOGGED_OUT = 'logged_out'
}
35 changes: 22 additions & 13 deletions src/components/picker/picker.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { Component, h, State } from '@stencil/core';
import { Component, EventEmitter, h, Event, State, Prop } from '@stencil/core';
import { onAuthStateChanged } from 'firebase/auth';
import { firebaseServiceInstance } from '../../firebase';

enum LoggedInState {
LOADING,
LOGGED_IN,
LOGGED_OUT
}
import { AuthState } from './auth-state.enum';

@Component({
tag: 'eva-config-picker',
Expand All @@ -16,26 +11,40 @@ enum LoggedInState {
export class Picker {

@State()
loggedIn: LoggedInState = LoggedInState.LOADING;
private authState = AuthState.LOADING;

/**
* will emit whenever the auth state changes
*/
@Event()
authStateChange: EventEmitter< AuthState>;

/**
* This will allow consumers to fetch the data themselves using the build in firebase service, ensuring they can match the look & feel of their application.
*/
@Prop()
hideCustomerPicker: boolean = false;

constructor() {

onAuthStateChanged(firebaseServiceInstance.auth, auth => {
if (!auth) {
this.loggedIn = LoggedInState.LOGGED_OUT;
this.authState = AuthState.LOGGED_OUT;
} else if (Boolean(auth.uid)) {
this.loggedIn = LoggedInState.LOGGED_IN;
this.authState = AuthState.LOGGED_IN;
}

this.authStateChange.emit(this.authState);
});
}


render() {
if (this.loggedIn === LoggedInState.LOADING) {
if (this.authState === AuthState.LOADING) {
return <eva-config-picker-spinner></eva-config-picker-spinner>;
} else if (this.loggedIn === LoggedInState.LOGGED_IN) {
} else if (this.authState === AuthState.LOGGED_IN && !this.hideCustomerPicker) {
return <eva-config-picker-customer></eva-config-picker-customer>;
} else if (this.loggedIn === LoggedInState.LOGGED_OUT) {
} else if (this.authState === AuthState.LOGGED_OUT) {
return <eva-config-picker-login></eva-config-picker-login>;
}
}
Expand Down
73 changes: 71 additions & 2 deletions src/firebase.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { getAuth } from "@firebase/auth";
import { getFirestore } from "@firebase/firestore";
import { getAuth, signOut } from "@firebase/auth";
import { collection, getDoc, getDocs, getFirestore } from "@firebase/firestore";
import { initializeApp } from "firebase/app";
import { getStorage } from "firebase/storage";
import { BaseEnvironment, SummarisedViewCustomer, ExtendedViewCustomer } from "./typings";

class FirebaseService {

app = initializeApp({
apiKey: "AIzaSyD51D-mGBu-wAOxckCZO2-dk5IRjrYhNlI",
authDomain: "eva-customer-manager.firebaseapp.com",
Expand All @@ -18,6 +21,72 @@ class FirebaseService {
storage = getStorage(this.app);

firestore = getFirestore(this.app);

signOut() {
return signOut(this.auth);
}

/**
* Will get a summary of all customers without their base environments fetched
* @returns
*/
async getViewCustomersSummary(): Promise<SummarisedViewCustomer[]> {
const customersSnapshot = await getDocs(collection(this.firestore, 'customer'));

const customerDocuments = customersSnapshot.docs;

const viewCustomersSummary: SummarisedViewCustomer[] = [];

for (const customerDocument of customerDocuments) {
try {
const customerData = customerDocument.data();

// We don't want the generic customer in the selection UI
//
if (customerData.name !== 'generic') {
const viewCustomer: SummarisedViewCustomer = {
name: customerData.name,
logoPath: customerData.logoPath,
baseEnvironments: customerData.baseEnvironments
}

viewCustomersSummary.push(viewCustomer);
}

} catch (error) {
console.error('[firebase-service:getViewCustomersSummary] error getting customer data for', customerDocument);
}

}

const sortedViewCustomersSummary = viewCustomersSummary.sort((a, b) => a.name.localeCompare(b.name));

return sortedViewCustomersSummary;
}

/**
* Takes in a customer summary as input and returns the extended view customer. Extended here being an object with the base environments fetched.
* @param viewCustomerSummary
* @returns
*/
async getExtendedViewCustomer(viewCustomerSummary: SummarisedViewCustomer): Promise<ExtendedViewCustomer> {
const baseEnvironments: BaseEnvironment[] = [];

for (const baseEnvironment of viewCustomerSummary.baseEnvironments ) {
const baseEnvironmentDocumentData = await getDoc(baseEnvironment);

const baseEnvironmentData = baseEnvironmentDocumentData.data() as BaseEnvironment;

baseEnvironments.push(baseEnvironmentData);
}

const extendedViewCustomer : ExtendedViewCustomer = {
...viewCustomerSummary,
baseEnvironments
}

return extendedViewCustomer;
}
}

export const firebaseServiceInstance = new FirebaseService();
Expand Down
6 changes: 5 additions & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
</head>
<body>
<eva-config-picker></eva-config-picker>
<!-- <eva-config-picker-spinner></eva-config-picker-spinner> -->
</body>
</html>

<script>
document.querySelector('eva-config-picker').addEventListener('onEndPointSelect', e => {
document.querySelector('eva-config-picker').addEventListener('endPointSelect', e => {
console.log(e);
})
document.querySelector('eva-config-picker').addEventListener('authStateChange', e => {
console.log(e);
});
</script>
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './components';
export { BaseEnvironment } from './components/picker-customers/picker-customers';
export * from './typings';
31 changes: 31 additions & 0 deletions src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { DocumentReference} from "@firebase/firestore";

export { AuthState } from './components/picker/auth-state.enum'

export interface BaseCustomer {
logoPath: string;
name: string;
}


/**
* This will contain a summary of the customer, with only a reference to the base environments
*/
export interface SummarisedViewCustomer extends BaseCustomer {
baseEnvironments: DocumentReference[];
}


/**
* will contain all the information in the summary, but with the base environments fetched
*/
export interface ExtendedViewCustomer extends BaseCustomer {
baseEnvironments: BaseEnvironment[];
}

export interface BaseEnvironment {
endpoint: string;
type: string;
customerName: string;
}

7 changes: 1 addition & 6 deletions stencil.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,5 @@ export const config: Config = {
],
plugins: [
sass()
],
commonjs: {
namedExports: {
'node_modules/idb/build/idb.js': ['openDb']
}
}
]
};

0 comments on commit a232e4f

Please sign in to comment.