Create Angular version (#1)

Convert site to Angular; also reworked contents on solution pages
This commit is contained in:
Daniel J. Summers 2019-11-14 22:22:18 -06:00 committed by GitHub
parent 7dc609cf94
commit cfaf2e84f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
176 changed files with 6214 additions and 5796 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ selenium-debug.log
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
.ionide/

View File

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/app'
]
}

View File

@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

46
bit-badger-solutions/.gitignore vendored Normal file
View File

@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,129 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"bit-badger-solutions": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "sass"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/bit-badger-solutions",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.sass"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "bit-badger-solutions:build"
},
"configurations": {
"production": {
"browserTarget": "bit-badger-solutions:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "bit-badger-solutions:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.sass"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "bit-badger-solutions:serve"
},
"configurations": {
"production": {
"devServerTarget": "bit-badger-solutions:serve:production"
}
}
}
}
}},
"defaultProject": "bit-badger-solutions"
}

View File

@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -0,0 +1,32 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('bit-badger-solutions app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText() {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}

View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/bit-badger-solutions'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

View File

@ -0,0 +1,47 @@
{
"name": "bit-badger-solutions",
"version": "3.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~8.2.13",
"@angular/common": "~8.2.13",
"@angular/compiler": "~8.2.13",
"@angular/core": "~8.2.13",
"@angular/forms": "~8.2.13",
"@angular/platform-browser": "~8.2.13",
"@angular/platform-browser-dynamic": "~8.2.13",
"@angular/router": "~8.2.13",
"rxjs": "~6.4.0",
"tslib": "^1.10.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.803.18",
"@angular/cli": "~8.3.18",
"@angular/compiler-cli": "~8.2.13",
"@angular/language-service": "~8.2.13",
"@types/node": "~8.9.4",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.5.3"
}
}

View File

@ -0,0 +1,28 @@
import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'
import { ApplicationComponent } from './applications/application.component'
import { ApplicationListComponent } from './applications/application-list/application-list.component'
import { HomeComponent } from './pages/home/home.component'
import { InformationPublicizingComponent } from './pages/about/information-publicizing.component'
import { LegacyDataComponent } from './pages/about/legacy-data.component'
import { ProcessAutomationComponent } from './pages/about/process-automation.component'
import { WebServicesComponent } from './pages/about/web-services.component'
import { WhyBitBadgerComponent } from './pages/about/why-bit-badger.component'
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about/information-publicizing-solutions', component: InformationPublicizingComponent },
{ path: 'about/legacy-data', component: LegacyDataComponent },
{ path: 'about/process-automation-solutions', component: ProcessAutomationComponent },
{ path: 'about/web-services-solutions', component: WebServicesComponent },
{ path: 'about/why-bit-badger', component: WhyBitBadgerComponent },
{ path: 'solutions', component: ApplicationListComponent },
{ path: 'solutions/:appId', component: ApplicationComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' })],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -0,0 +1,5 @@
<app-header></app-header>
<div id="content">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>

View File

@ -0,0 +1,35 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'bit-badger-solutions'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('bit-badger-solutions');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('bit-badger-solutions app is running!');
});
});

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass']
})
export class AppComponent {
title = 'bit-badger-solutions';
}

View File

@ -0,0 +1,39 @@
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { AppRoutingModule } from './app-routing.module'
import { ApplicationsModule } from './applications/applications.module'
import { SharedModule } from './shared/shared.module'
import { SidebarModule } from './sidebar/sidebar.module'
import { AppComponent } from './app.component'
import { HomeComponent } from './pages/home/home.component'
import { WhyBitBadgerComponent } from './pages/about/why-bit-badger.component'
import { InformationPublicizingComponent } from './pages/about/information-publicizing.component'
import { LegacyDataComponent } from './pages/about/legacy-data.component'
import { ProcessAutomationComponent } from './pages/about/process-automation.component'
import { WebServicesComponent } from './pages/about/web-services.component'
@NgModule({
declarations: [
AppComponent,
HomeComponent,
WhyBitBadgerComponent,
InformationPublicizingComponent,
LegacyDataComponent,
ProcessAutomationComponent,
WebServicesComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
ApplicationsModule,
SharedModule,
SidebarModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,13 @@
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-all-solutions-link',
template: `<p><br><a routerLink="/solutions">&laquo; Back to All Solutions</a></p>`
})
export class AllSolutionsLinkComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -0,0 +1,6 @@
import { Type } from '@angular/core'
/** An item representing an app */
export class AppItem {
constructor(public name: string, public component: Type<any>) { }
}

View File

@ -0,0 +1,10 @@
import { Directive, ViewContainerRef } from '@angular/core'
@Directive({
selector: '[app-application-detail]'
})
export class ApplicationDetailDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}

View File

@ -0,0 +1,10 @@
<h1>
{{ app.name }}<br>
<small><small>
<a *ngIf="linkToApp" [href]="app.url">{{ app.url }}</a>
<span *ngIf="!linkToApp">{{ app.url }}</span>
<span *ngIf="linkToArchive">&nbsp; &nbsp;
<a [href]="app.archiveUrl"><small>(Archive)</small></a>
</span>
</small></small>
</h1>

View File

@ -0,0 +1,2 @@
h1
line-height: 1.6rem

View File

@ -0,0 +1,28 @@
import { Component, OnInit, Input } from '@angular/core'
import { App } from '../application.types'
@Component({
selector: 'app-application-header',
templateUrl: './application-header.component.html',
styleUrls: ['./application-header.component.sass']
})
export class ApplicationHeaderComponent implements OnInit {
@Input() app: App
constructor() { }
ngOnInit() { }
/** Whether to link to the app's URL */
get linkToApp () {
return this.app.isActive || this.app.linkInactive
}
/** Whether to link to an archive URL */
get linkToArchive () {
return !this.app.isActive && !this.app.linkInactive && (this.app.archiveUrl > '')
}
}

View File

@ -0,0 +1 @@
<aside><span>&nbsp;</span><img [src]="imageLink" [alt]="imageAlt"></aside>

View File

@ -0,0 +1,9 @@
aside
float: right
background-color: #FFFAFA
aside > span
padding-left: .75rem
aside > img
overflow: hidden
border: dotted 1px darkgray
border-radius: 10px

View File

@ -0,0 +1,29 @@
import { Component, OnInit, Input } from '@angular/core'
import { App } from '../application.types'
@Component({
selector: 'app-application-image',
templateUrl: './application-image.component.html',
styleUrls: ['./application-image.component.sass']
})
export class ApplicationImageComponent implements OnInit {
@Input() app: App
constructor() { }
ngOnInit() {
}
/** The link to the screenshot image */
get imageLink () {
return `/assets/screenshots/${this.app.id}.png`
}
/** The alt text for the screenshot image */
get imageAlt () {
return `Screen shot for ${this.app.name}`
}
}

View File

@ -0,0 +1,9 @@
<p>
<span class="app-name" [innerHtml]="app.name"></span>&nbsp;~&nbsp;<a routerLink="/solutions/{{ app.id }}">About</a>
<span *ngIf="app.isActive">&nbsp;~&nbsp;<a [href]="app.url">Visit</a></span>
<span *ngIf="!app.isActive && app.archiveUrl">
~&nbsp;<a [href]="app.archiveUrl">Visit</a><em> (archive)</em>
</span>
<br>
<span [innerHtml]="app.indexText"></span>
</p>

View File

@ -0,0 +1,5 @@
.app-name
font-family: "Oswald", "Segoe UI", Ubuntu, "DejaVu Sans", "Liberation Sans", Arial, sans-serif
font-size: 1.3rem
font-weight: bold
color: maroon

View File

@ -0,0 +1,18 @@
import { Component, OnInit, Input } from '@angular/core'
import { App } from '../application.types'
@Component({
selector: 'app-application-list-item',
templateUrl: './application-list-item.component.html',
styleUrls: ['./application-list-item.component.sass']
})
export class ApplicationListItemComponent implements OnInit {
@Input() app: App
constructor() { }
ngOnInit() { }
}

View File

@ -0,0 +1,8 @@
<app-page-title title="All Solutions"></app-page-title>
<article class="content auto">
<h1>All Solutions</h1>
<h2>Active Solutions</h2>
<app-application-list-item *ngFor="let app of current" [app]="app"></app-application-list-item>
<h2>Past Solutions</h2>
<app-application-list-item *ngFor="let app of past" [app]="app"></app-application-list-item>
</article>

View File

@ -0,0 +1,25 @@
import { Component, OnInit } from '@angular/core'
import { ApplicationService } from '../application.service'
import { App } from '../application.types'
@Component({
selector: 'app-application-list',
templateUrl: './application-list.component.html'
})
export class ApplicationListComponent implements OnInit {
current: App[]
past: App[]
constructor(private appService: ApplicationService) { }
ngOnInit() {
this.appService.getApps().subscribe(apps => {
this.current = apps.filter(app => app.isActive && !app.noAboutLink)
this.past = apps.filter(app => !app.isActive && !app.noAboutLink)
})
}
}

View File

@ -0,0 +1,87 @@
import { Component, OnInit, ViewChild, ComponentFactoryResolver } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { ApplicationService } from './application.service'
import { App } from './application.types'
import { AppItem } from './app-item'
import { ApplicationDetailDirective } from './application-detail.directive'
import { AppDetailComponent } from './solutions/app-detail.component'
import { BayVistaComponent } from './solutions/bay-vista.component'
import { BitBadgerBlogComponent } from './solutions/bit-badger-blog.component'
import { CassyFianoComponent } from './solutions/cassy-fiano.component'
import { DrMelissaClouthierComponent } from './solutions/dr-melissa-clouthier.component'
import { EmeraldMountainChristianSchoolComponent } from './solutions/emerald-mountain-christian-school.component'
import { FutilityClosetComponent } from './solutions/futility-closet.component'
import { HardCorpsWifeComponent } from './solutions/hard-corps-wife.component'
import { LibertyPunditsComponent } from './solutions/liberty-pundits.component'
import { MindyMackenzieComponent } from './solutions/mindy-mackenzie.component'
import { MyPrayerJournalComponent } from './solutions/my-prayer-journal.component'
import { NsxComponent } from './solutions/nsx.component'
import { OlivetBaptistComponent } from './solutions/olivet-baptist.component'
import { PhotographyByMichelleComponent } from './solutions/photography-by-michelle.component'
import { PrayerTrackerComponent } from './solutions/prayer-tracker.component'
import { RiehlWorldNewsComponent } from './solutions/riehl-world-news.component'
import { SharkTankComponent } from './solutions/shark-tank.component'
import { TcmsComponent } from './solutions/tcms.component'
import { VirtualPrayerRoomComponent } from './solutions/virtual-prayer-room.component'
@Component({
selector: 'app-application',
template: '<ng-template app-application-detail></ng-template>'
})
export class ApplicationComponent implements OnInit {
private apps = [
new AppItem('bay-vista', BayVistaComponent),
new AppItem('cassy-fiano', CassyFianoComponent),
new AppItem('dr-melissa-clouthier', DrMelissaClouthierComponent),
new AppItem('emerald-mountain-christian-school', EmeraldMountainChristianSchoolComponent),
new AppItem('futility-closet', FutilityClosetComponent),
new AppItem('hard-corps-wife', HardCorpsWifeComponent),
new AppItem('liberty-pundits', LibertyPunditsComponent),
new AppItem('mindy-mackenzie', MindyMackenzieComponent),
new AppItem('my-prayer-journal', MyPrayerJournalComponent),
new AppItem('nsx', NsxComponent),
new AppItem('olivet-baptist', OlivetBaptistComponent),
new AppItem('photography-by-michelle', PhotographyByMichelleComponent),
new AppItem('prayer-tracker', PrayerTrackerComponent),
new AppItem('riehl-world-news', RiehlWorldNewsComponent),
new AppItem('the-shark-tank', SharkTankComponent),
new AppItem('tcms', TcmsComponent),
new AppItem('tech-blog', BitBadgerBlogComponent),
new AppItem('virtual-prayer-room', VirtualPrayerRoomComponent)
]
@ViewChild(ApplicationDetailDirective, { static: true }) appDetail: ApplicationDetailDirective
/** The app we're displaying */
application: App
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private appService: ApplicationService,
private route: ActivatedRoute
) { }
ngOnInit() {
this.route.params.subscribe(params => this.displayApp(params['appId']))
}
/** Dynamically load the app-ropriate component */
displayApp(appId: string) {
const appComponent = this.apps.find(a => a.name === appId)
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(appComponent.component)
const viewContainerRef = this.appDetail.viewContainerRef
viewContainerRef.clear()
const componentRef = viewContainerRef.createComponent(componentFactory)
this.appService.getApp(appId)
.subscribe(app => {
(<AppDetailComponent>componentRef.instance).app = app
this.application = app
})
}
}

View File

@ -0,0 +1,323 @@
import { App, Category, Quote, Technology } from './application.types'
/** A Word from the Word */
const aWordFromTheWord = new App('a-word-from-the-word', 'A Word from the Word', 'https://devotions.summershome.org')
aWordFromTheWord.categoryId = Category.PERSONAL
aWordFromTheWord.noAboutLink = true
aWordFromTheWord.frontPageText = 'Devotions by Daniel'
aWordFromTheWord.frontPageOrder = 2
/** Bay Vista Baptist Church */
const bayVista = new App('bay-vista', 'Bay Vista Baptist Church', 'https://bayvista.org')
bayVista.categoryId = Category.STATIC
bayVista.frontPageText = 'Biloxi, Mississippi'
bayVista.frontPageOrder = 1
bayVista.indexText = 'Southern Baptist church in Biloxi, Mississippi'
bayVista.techStack = [
new Technology('Hugo', 'static site generation', true),
new Technology('Azure', 'podcast file storage, automated builds, and static site hosting', true),
new Technology('GitHub', 'source code control', true),
new Technology('Hexo', 'static site generation'),
new Technology('Jekyll', 'static site generation'),
new Technology('WordPress', 'content management'),
new Technology('MySQL', 'data storage')
]
/** The Bit Badger Blog */
const techBlog = new App('tech-blog', 'The Bit Badger Blog', 'https://blog.bitbadger.solutions')
techBlog.categoryId = Category.STATIC
techBlog.frontPageText = 'Technical information (&ldquo;geek stuff&rdquo;) from Bit Badger Solutions'
techBlog.frontPageOrder = 3
techBlog.indexText = 'Geek stuff from Bit Badger Solutions'
techBlog.techStack = [
new Technology('Hexo', 'static site generation', true),
new Technology('Azure', 'static site hosting', true),
new Technology('GitHub', 'source code control', true),
new Technology('Custom software', 'content management'),
new Technology('WordPress', 'content management'),
new Technology('BlogEngine.NET', 'content management'),
new Technology('Orchard', 'content management'),
new Technology('myWebLog', 'content management'),
new Technology('Jekyll', 'static site generation'),
new Technology('MySQL', 'data storage'),
new Technology('SQL Server', 'data storage'),
new Technology('RethinkDB', 'data storage')
]
/** Cassy Fiano */
const cassyFiano = new App('cassy-fiano', 'Cassy Fiano', 'http://www.cassyfiano.com')
cassyFiano.isActive = false
cassyFiano.categoryId = Category.WORDPRESS
cassyFiano.indexText = 'A &ldquo;rising star&rdquo; conservative blogger'
cassyFiano.techStack = [
new Technology('WordPress', 'blogging (with a custom theme)'),
new Technology('MySQL', 'data storage'),
new Technology('Rackspace Cloud', 'backup and recovery'),
new Technology('Azure', 'backup and recovery')
]
/** Daniel J. Summers */
const danielJSummers = new App('daniel-j-summers', 'Daniel J. Summers', 'https://daniel.summershome.org')
danielJSummers.categoryId = Category.PERSONAL
danielJSummers.noAboutLink = true
danielJSummers.frontPageText = 'Daniel&rsquo;s personal blog',
danielJSummers.frontPageOrder = 1
/** Dr. Melissa Clouthier */
const drMelissaClouthier = new App('dr-melissa-clouthier', 'Dr. Melissa Clouthier', 'http://melissablogs.com')
drMelissaClouthier.isActive = false
drMelissaClouthier.categoryId = Category.WORDPRESS
drMelissaClouthier.frontPageText = 'Information Pollination'
drMelissaClouthier.frontPageOrder = 1
drMelissaClouthier.indexText = 'Politics, health, podcasts and more'
drMelissaClouthier.techStack = [
new Technology('WordPress', 'blogging (with a custom theme)'),
new Technology('MySQL', 'data storage'),
new Technology('Rackspace Cloud', 'backup and recovery'),
new Technology('Azure', 'backup and recovery')
]
/** Emerald Mountain Christian School */
const emcs = new App('emerald-mountain-christian-school', 'Emerald Mountain Christian School',
'http://www.emeraldmountainchristianschool.org')
emcs.isActive = false
emcs.linkInactive = true
emcs.indexText = 'Classical, Christ-centered education near Wetumpka, Alabama'
emcs.techStack = [
new Technology('PHP', 'page generation and interactivity'),
new Technology('ASP.NET MVC', 'page generation and interactivity'),
new Technology('PostgreSQL', 'data storage'),
new Technology('Rackspace Cloud', 'hosting'),
new Technology('Azure', 'hosting')
]
/** Futility Closet */
const futilityCloset = new App('futility-closet', 'Futility Closet', 'https://www.futilitycloset.com')
futilityCloset.categoryId = Category.WORDPRESS
futilityCloset.frontPageText = 'An idler&rsquo;s miscellany of compendious amusements'
futilityCloset.frontPageOrder = 2
futilityCloset.indexText = 'An idler&rsquo;s miscellany of compendious amusements'
const fcQuote = new Quote('Greg Ross', 'Futility Closet')
fcQuote.full =
`Bit Badger Solutions has been an absolute godsend for Futility Closet. We have been with them since 2010, initially
setting up and maintaining the site on a Rackspace VPS, and then hosting it completely. Daniel&rsquo;s never failed
in being friendly, knowledgeable, thoughtful, and farsighted. I&rsquo;ve literally lost count of the number of times
he&rsquo;s saved us from one emergency or another, always with diligence and good humor, or recommended an
improvement or a protection that saved us later. We would be out of business many times over if it weren&rsquo;t for
his reliability, expertise, and good judgment. And he&rsquo;s a joy to work with.`
fcQuote.pull = [
`Daniel&rsquo;s never failed in being friendly, knowledgeable, thoughtful, and farsighted&hellip;`,
`We would be out of business many times over if it weren&rsquo;t for his reliability, expertise, and good
judgment&hellip;`
]
futilityCloset.quotes.push(fcQuote)
futilityCloset.techStack = [
new Technology('WordPress', 'blogging', true),
new Technology('nginx', 'the web server', true),
new Technology('MySQL', 'data storage', true),
new Technology('Digital Ocean', 'web site hosting', true),
new Technology('Azure', 'backup and recovery', true),
new Technology('Rackspace Cloud', 'web site hosting')
]
/** Hard Corps Wife */
const hardCorpsWife = new App('hard-corps-wife', 'Hard Corps Wife', 'http://www.hardcorpswife.com')
hardCorpsWife.isActive = false
hardCorpsWife.categoryId = Category.WORDPRESS
hardCorpsWife.indexText = 'Cassy&rsquo;s life as a Marine wife'
hardCorpsWife.techStack = [
new Technology('WordPress', 'blogging'),
new Technology('MySQL', 'data storage'),
new Technology('Rackspace Cloud', 'web site hosting')
]
/** Liberty Pundits */
const libertyPundits = new App('liberty-pundits', 'Liberty Pundits', 'http://libertypundits.net')
libertyPundits.isActive = false
libertyPundits.categoryId = Category.WORDPRESS
libertyPundits.indexText = 'The home for conservatives'
libertyPundits.techStack = [
new Technology('WordPress', 'blogging'),
new Technology('PHP', 'custom data migration software'),
new Technology('MySQL', 'data storage')
]
/** Linux Resources */
const linuxResources = new App('linux', 'Linux Resources', 'https://blog.bitbadger.solutions/linux/')
linuxResources.noAboutLink = true
linuxResources.frontPageText = 'Handy information for Linux folks'
linuxResources.frontPageOrder = 3
/** Mindy Mackenzie */
const mindyMackenzie = new App('mindy-mackenzie', 'Mindy Mackenzie', 'https://mindymackenzie.com')
mindyMackenzie.categoryId = Category.WORDPRESS
mindyMackenzie.frontPageText = 'WSJ-best-selling author of The Courage Solution'
mindyMackenzie.frontPageOrder = 3
mindyMackenzie.indexText = '<em>Wall Street Journal</em> best-selling author and C-suite advisor'
const mmQuote = new Quote('Mindy Mackenzie', '')
mmQuote.full =
`Daniel is the best partner you could hope for in a web designer and for handling web maintenance! He is smart,
creative, resourceful and fast. Daniel is able to produce high quality work on short time frames and with minimal
creative direction and hit the mark over and over. The best part, is Daniel is a joy to work with. He is smart,
customer-centric and trustworthy. If he says he will get it done, he does. After having a poor experience with
another firm, I can highly recommend Daniel for all your website design and support needs he&rsquo;s terrific!`
mmQuote.pull = [
'&hellip;Daniel is able to produce high quality work on short time frames&hellip;',
'[Daniel] is smart, customer-centric and trustworthy.'
]
mindyMackenzie.quotes.push(mmQuote)
mindyMackenzie.techStack = [
new Technology('WordPress', 'blogging', true),
new Technology('nginx', 'the web server', true),
new Technology('MySQL', 'data storage', true),
new Technology('Digital Ocean', 'web site hosting', true),
new Technology('Azure', 'backup and recovery', true),
]
/** myPrayerJournal */
const myPrayerJournal = new App('my-prayer-journal', 'myPrayerJournal', 'https://prayerjournal.me')
myPrayerJournal.frontPageText = 'Minimalist personal prayer journal'
myPrayerJournal.frontPageOrder = 2
myPrayerJournal.indexText = 'Minimalist personal prayer journal'
myPrayerJournal.techStack = [
new Technology('Vue.js', 'the front-end', true),
new Technology('Giraffe', 'the back-end data API', true),
new Technology('RavenDB', 'data storage', true),
new Technology('GitHub', 'source code control', true),
new Technology('GitHub Pages', 'documentation', true),
new Technology('PostgreSQL', 'data storage')
]
/** Not So Extreme Makeover: Community Edition */
const nsx = new App('nsx', 'Not So Extreme Makeover: Community Edition', 'http://notsoextreme.org')
nsx.isActive = false
nsx.archiveUrl = 'https://nsx.archive.bitbadger.solutions'
nsx.indexText =
'Public site for the makeover; provides event-driven management of volunteers, donations, and families needing help'
nsx.techStack = [
new Technology('WordPress', 'content management'),
new Technology('PHP', 'NSXapp'),
new Technology('MySQL', 'WordPress data storage'),
new Technology('PostgreSQL', 'NSXapp data storage')
]
/** Olivet Baptist Church */
const olivet = new App('olivet-baptist', 'Olivet Baptist Church', 'https://olivet-baptist.org')
olivet.isActive = false
olivet.archiveUrl = 'https://olivet.archive.bitbadger.solutions'
olivet.categoryId = Category.STATIC
olivet.indexText = 'Southern Baptist church in Gulfport, Mississippi'
olivet.techStack = [
new Technology('Vue.js', 'the user interface for the PWA'),
new Technology('Hexo', `generating the site's pages`),
new Technology('Azure', 'podcast file storage and archive site hosting'),
new Technology('WordPress', 'content management'),
new Technology('MySQL', 'data storage')
]
/** Photography by Michelle */
const photographyByMichelle = new App('photography-by-michelle', 'Photography by Michelle',
'https://www.summershome.org')
photographyByMichelle.isActive = false
photographyByMichelle.linkInactive = true
photographyByMichelle.indexText = 'Photography services in Albuquerque, New Mexico'
photographyByMichelle.techStack = [
new Technology('ASP.NET MVC', 'content management / gallery creation API'),
new Technology('PostgreSQL', 'data storage'),
new Technology('C# / Windows Forms', 'desktop gallery application'),
new Technology('WordPress', 'content management'),
new Technology('MySQL', 'data storage')
]
/** PrayerTracker */
const prayerTracker = new App('prayer-tracker', 'PrayerTracker', 'https://prayer.bitbadger.solutions')
prayerTracker.frontPageText = 'A prayer request tracking website (Free for any church or Sunday School class!)'
prayerTracker.frontPageOrder = 1
prayerTracker.indexText = 'Provides an ongoing, centralized prayer list for Sunday School classes and other groups'
prayerTracker.techStack = [
new Technology('Giraffe', 'server-side logic and dynamic page generation', true),
new Technology('PostgreSQL', 'data storage', true),
new Technology('GitHub', 'source code control', true),
new Technology('GitHub Pages', 'documentation hosting', true),
new Technology('MongoDB', 'data storage'),
new Technology('ASP.NET MVC', 'dynamic content generation'),
new Technology('Database Abstraction', 'data access'),
new Technology('MySQL', 'data storage'),
new Technology('PHP', 'dynamic content generation')
]
/** Riehl World News */
const riehlWorldNews = new App('riehl-world-news', 'Riehl World News', 'http://riehlworldview.com')
riehlWorldNews.categoryId = Category.WORDPRESS
riehlWorldNews.frontPageText = 'Riehl news for real people'
riehlWorldNews.frontPageOrder = 4
riehlWorldNews.indexText = 'Riehl news for real people'
riehlWorldNews.techStack = [
new Technology('WordPress', 'blogging', true),
new Technology('MySQL', 'data storage', true),
new Technology('F#', 'custom archive static page generation')
]
/** The Shark Tank */
const theSharkTank = new App('the-shark-tank', 'The Shark Tank', 'http://shark-tank.net')
theSharkTank.isActive = false
theSharkTank.categoryId = Category.WORDPRESS
theSharkTank.indexText = 'Floridas political feeding frenzy'
theSharkTank.techStack = [ new Technology('WordPress', 'blogging') ]
/** The Clearinghouse Management System */
var tcms = new App('tcms', 'The Clearinghouse Management System', 'http://tcms.us')
tcms.isActive = false
tcms.indexText =
'Assists a needs clearinghouse in connecting people with needs to people that can help meet those needs'
tcms.techStack = [
new Technology('PHP', 'the TCMS application logic'),
new Technology('WordPress', 'publicly-facing pages and authentication'),
new Technology('PostgreSQL', 'application data storage'),
new Technology('MySQL', 'WordPress data storage')
]
/** Virtual Prayer Room */
const vpr = new App('virtual-prayer-room', 'Virtual Prayer Room', 'https://virtualprayerroom.us')
vpr.isActive = false
vpr.indexText = 'Gives prayer warriors access to requests from wherever they may be, and sends them daily updates'
vpr.techStack = [
new Technology('PHP', 'the application logic'),
new Technology('PostgreSQL', 'data storage')
]
export default {
/** All categories */
categories: [
new Category(Category.SITES_APPS, 'Web Sites and Applications'),
new Category(Category.WORDPRESS, 'WordPress'),
new Category(Category.STATIC, 'Static Sites'),
new Category(Category.PERSONAL, 'Personal')
],
/** All apps */
apps: [
aWordFromTheWord,
bayVista,
cassyFiano,
danielJSummers,
drMelissaClouthier,
emcs,
futilityCloset,
hardCorpsWife,
libertyPundits,
linuxResources,
mindyMackenzie,
myPrayerJournal,
nsx,
olivet,
photographyByMichelle,
prayerTracker,
riehlWorldNews,
tcms,
techBlog,
theSharkTank,
vpr
]
}

View File

@ -0,0 +1,48 @@
import { Injectable } from '@angular/core'
import { Observable, of } from 'rxjs'
import Data from './application.data'
import { Category, App } from './application.types'
@Injectable({
providedIn: 'root'
})
export class ApplicationService {
constructor() { }
/**
* Get all categories of apps
*/
getCategories(): Observable<Category[]> {
return of(Data.categories)
}
/**
* Get all apps
*/
getApps(): Observable<App[]> {
return of(Data.apps)
}
/**
* Get all applications for the given category ID
* @param categoryId The ID of the category for which apps should be retrieved
*/
getAppsForCategory(categoryId: number): Observable<App[]> {
return of(
Data.apps
.filter(app => app.categoryId === categoryId)
.sort((a, b) => a.frontPageOrder - b.frontPageOrder)
)
}
/**
* Get a specific app
* @param appId The ID of the app to retrieve
*/
getApp(appId: string): Observable<App> {
return of(Data.apps.find(app => app.id === appId))
}
}

View File

@ -1,16 +1,3 @@
'use strict'
/** An activity performed for a customer */
export class Activity {
/**
* Construct a new instance
* @param heading The heading of the activity
* @param narrative The description of the activity
*/
constructor(public heading: string, public narrative: string) { }
}
/** A category of application */ /** A category of application */
export class Category { export class Category {
@ -61,8 +48,9 @@ export class Technology {
* Construct a new instace * Construct a new instace
* @param name The name of the technology * @param name The name of the technology
* @param usedFor What aspect was addressed by this technology * @param usedFor What aspect was addressed by this technology
* @param current Whether this technology is currently in use in the solution
*/ */
constructor(public name: string, public usedFor: string) { } constructor(public name: string, public usedFor: string, public current: boolean = false) { }
} }
/** An application or web site */ /** An application or web site */
@ -92,15 +80,6 @@ export class App {
/** The URL where an archived version of this app may be found */ /** The URL where an archived version of this app may be found */
archiveUrl: string = '' archiveUrl: string = ''
/** Paragraphs of text that describe the app */
paragraphs: string[] = []
/** Footnotes for the long description */
footnotes: string[] = []
/** Discrete activities performed for this app */
activities: Activity[] = []
/** The technology used for this app */ /** The technology used for this app */
techStack: Technology[] = [] techStack: Technology[] = []

View File

@ -0,0 +1,99 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { RouterModule } from '@angular/router'
import { AllSolutionsLinkComponent } from './all-solutions-link.component'
import { ApplicationComponent } from './application.component'
import { ApplicationDetailDirective } from './application-detail.directive'
import { ApplicationHeaderComponent } from './application-header/application-header.component'
import { ApplicationImageComponent } from './application-image/application-image.component'
import { ApplicationListComponent } from './application-list/application-list.component'
import { ApplicationListItemComponent } from './application-list-item/application-list-item.component'
import { HideSectionComponent } from './hide-section/hide-section.component'
import { QuotesComponent } from './quotes/quotes.component';
import { SharedModule } from '../shared/shared.module'
import { TechnologyComponent } from './technology/technology.component';
import { TechStackComponent } from './tech-stack/tech-stack.component';
import { BayVistaComponent } from './solutions/bay-vista.component'
import { BitBadgerBlogComponent } from './solutions/bit-badger-blog.component';
import { CassyFianoComponent } from './solutions/cassy-fiano.component'
import { DrMelissaClouthierComponent } from './solutions/dr-melissa-clouthier.component';
import { EmeraldMountainChristianSchoolComponent } from './solutions/emerald-mountain-christian-school.component';
import { FutilityClosetComponent } from './solutions/futility-closet.component';
import { HardCorpsWifeComponent } from './solutions/hard-corps-wife.component';
import { LibertyPunditsComponent } from './solutions/liberty-pundits.component';
import { MindyMackenzieComponent } from './solutions/mindy-mackenzie.component';
import { MyPrayerJournalComponent } from './solutions/my-prayer-journal.component';
import { NsxComponent } from './solutions/nsx.component';
import { OlivetBaptistComponent } from './solutions/olivet-baptist.component';
import { PhotographyByMichelleComponent } from './solutions/photography-by-michelle.component';
import { PrayerTrackerComponent } from './solutions/prayer-tracker.component'
import { RiehlWorldNewsComponent } from './solutions/riehl-world-news.component';
import { SharkTankComponent } from './solutions/shark-tank.component';
import { TcmsComponent } from './solutions/tcms.component';
import { VirtualPrayerRoomComponent } from './solutions/virtual-prayer-room.component'
@NgModule({
declarations: [
AllSolutionsLinkComponent,
ApplicationComponent,
ApplicationDetailDirective,
ApplicationHeaderComponent,
ApplicationImageComponent,
ApplicationListComponent,
ApplicationListItemComponent,
BayVistaComponent,
BitBadgerBlogComponent,
CassyFianoComponent,
DrMelissaClouthierComponent,
EmeraldMountainChristianSchoolComponent,
FutilityClosetComponent,
HardCorpsWifeComponent,
HideSectionComponent,
LibertyPunditsComponent,
MindyMackenzieComponent,
MyPrayerJournalComponent,
NsxComponent,
OlivetBaptistComponent,
PhotographyByMichelleComponent,
PrayerTrackerComponent,
QuotesComponent,
RiehlWorldNewsComponent,
SharkTankComponent,
TcmsComponent,
TechnologyComponent,
TechStackComponent,
VirtualPrayerRoomComponent
],
entryComponents: [
BayVistaComponent,
BitBadgerBlogComponent,
CassyFianoComponent,
DrMelissaClouthierComponent,
EmeraldMountainChristianSchoolComponent,
FutilityClosetComponent,
HardCorpsWifeComponent,
LibertyPunditsComponent,
MindyMackenzieComponent,
MyPrayerJournalComponent,
NsxComponent,
OlivetBaptistComponent,
PhotographyByMichelleComponent,
PrayerTrackerComponent,
RiehlWorldNewsComponent,
SharkTankComponent,
TcmsComponent,
VirtualPrayerRoomComponent
],
imports: [
CommonModule,
RouterModule,
SharedModule
],
exports: [
ApplicationComponent,
ApplicationListComponent
]
})
export class ApplicationsModule { }

View File

@ -0,0 +1,4 @@
<h3 (click)="toggle()">{{ heading }}<span class="arrow" [innerHtml]="label"></span></h3>
<div *ngIf="shown" [@slideInOut]>
<ng-content></ng-content>
</div>

View File

@ -0,0 +1,6 @@
h3:hover
cursor: hand
cursor: pointer
.arrow
font-size: .75rem
padding-left: 1rem

View File

@ -0,0 +1,38 @@
import { Component, OnInit, Input } from '@angular/core';
import { trigger, transition, style, animate, group } from '@angular/animations';
@Component({
selector: 'app-hide-section',
templateUrl: './hide-section.component.html',
styleUrls: ['./hide-section.component.sass'],
animations: [
trigger('slideInOut', [
transition(':enter', [
style({ opacity: 0 }),
animate('500ms ease-in', style({ opacity: 1 }))
]),
transition(':leave', [
style({opacity: 1}),
animate('500ms ease-in', style({ opacity: 0 }))
])
])
]
})
export class HideSectionComponent implements OnInit {
@Input() heading: string
label = '&#x25BC;'
shown = false
constructor() { }
ngOnInit() { }
toggle() {
this.shown = !this.shown
this.label = this.shown ? '&#x25B2;' : '&#x25BC;'
}
}

View File

@ -0,0 +1,10 @@
<div *ngIf="(quotes || []).length > 0">
<h3>The Business Impact</h3>
<blockquote *ngFor="let quote of quotes">
<p class="quote" [innerHtml]="quote.full"></p>
<p class="source">
&mdash; <strong>{{ quote.name }}</strong>
<span *ngIf="quote.from">, {{ quote.from }}</span>
</p>
</blockquote>
</div>

View File

@ -0,0 +1,9 @@
blockquote
border-left: solid 1px darkgray
margin-left: 25px
padding-left: 15px
.quote
font-style: italic
.source
text-align: right
padding-right: 60px

View File

@ -0,0 +1,18 @@
import { Component, OnInit, Input } from '@angular/core'
import { Quote } from '../application.types'
@Component({
selector: 'app-quotes',
templateUrl: './quotes.component.html',
styleUrls: ['./quotes.component.sass']
})
export class QuotesComponent implements OnInit {
@Input() quotes: Quote[]
constructor() { }
ngOnInit() { }
}

View File

@ -0,0 +1,14 @@
import { App } from '../application.types'
/** An inteface implemented by all app detail components */
export class AppDetailComponent {
/** The app to be displayed */
app: App
/** The page title based on this app */
get pageTitle () {
return `${this.app.name} « Solutions`
}
}

View File

@ -0,0 +1,47 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Bay Vista Baptist Church has served the spiritual needs of Mississippi&rsquo;s Gulf Coast for decades. They
emphasize serving their community as well; they were a hub for
<abbr title="Federal Emergency Management Agency">FEMA</abbr> during Hurricane Katrina relief and recovery
efforts, and they are a relay point for each year&rsquo;s
<a href="https://www.samaritanspurse.org/what-we-do/operation-christmas-child/">Operation Christmas Child</a>
campaign.
</p>
<h3>The Problem</h3>
<p>
In late 2013, the authors of their current website were no longer around, and no one could get to the site to
update it.
</p>
<h3>The Solution</h3>
<p>
We developed and continue to maintain a fast, static website that can be updated by multiple trained church
members. The site also has a repository for their sermons dating back to January 2014, and a podcast feed that
gives their ministry a global reach.
</p>
<app-hide-section heading="The Process">
<p>
Initially, we set up a WordPress-based site, where multiple people could have the ability to maintain the site.
We manually downloaded all the publically-accessible parts of their old site, and used that content to form the
basis for the new side, updating outdated information along the way. We maintained the same look-and-feel, but
soon moved to a more mobile-friendly layout.
</p>
<p>
In 2016, we determined that we were the only ones updating the site, so we transformed the site to use a static
site generator; this resulted in fast page loads, with automation providing scheduled updates. We also wrote a
custom template for the podcast feed, which is also generated as a static file.
</p>
<p>
In 2019, we <a href="https://github.com/bayvistabc/www.bayvista.org">open sourced</a> the site's source code.
We also set up Azure Pipelines to automatically build and deploy the site both on demand and on a schedule.
Finally, we trained other church members on updating the site's contents and the podcast feed.
</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-bay-vista',
templateUrl: './bay-vista.component.html'
})
export class BayVistaComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,39 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Problem</h3>
<p>
Daniel needed a place to journal his learning journey with the Linux operating system, and thought that allowing
others read this journal would help them learn as well.
</p>
<h3>The Solution</h3>
<p>
<em>The Bit Badger Blog</em> contains that journal, plus tech tips and information for many different aspects of
technology. It is written, maintained, and hosted by Bit Badger Solutions.
</p>
<app-hide-section heading="The Process">
<p>
The initial posts were titled &ldquo;My Linux Adventure,&rdquo; and existed as static files that were edited to
add each post. Daniel then wrote a rudimentary system that stored the posts in a database, which meant that the
entire site did not need manual changes &ndash; what a breakthrough! :)
</p>
<p>
Over time, the <em>Bit Badger Blog</em> (and the <em>DJS Consulting Tech Blog</em> before it) has served as a
place to support <em>(now inactive)</em> WordPress plug-ins, and go in depth on servers, databases, programming
languages, and open-source software. It has also served as a useful live website for learning and
experimentation with different content management systems and blogging tools. It has existed in at least 8
different tools, with links preserved as systems change.
</p>
<p>
It is currently a statically-generated site, utilizing <a href="https://hexo.io">Hexo</a>, and its code is
<a href="https://github.com/bit-badger/blog.bitbadger.solutions">open source</a>. New posts are infrequent,
but the information it has is good. It may have more behind-the-scenes posts about future open-source efforts.
Stay tuned!
</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-bit-badger-blog',
templateUrl: './bit-badger-blog.component.html'
})
export class BitBadgerBlogComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,39 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Cassy Fiano (now Cassy Chesser) began blogging back in 2007 on Blogger. She worked hard to network with other
bloggers, wrote prolifically, and gained a large audience with her coverage of life issues and of Sarah Palin as
the first female Republican vice-presidential nominee.
</p>
<h3>The Problem</h3>
<p>
With her success, Cassy was quickly outgrowing Blogger. She was interested in moving to a different platform;
specifically, Movable Type, as she had some authoring experience with that platform.
</p>
<h3>The Solution</h3>
<p>
We migrated her content to a WordPress site, and customized a theme to look very similar to her Blogger theme
(which she liked). We maintained the site, and began hosting it a few years later.
</p>
<h3>The Epilogue</h3>
<p>Cassy formally decommissioned this site in early 2014.</p>
<app-hide-section heading="The Process">
<p>
Initially, we assisted her with finding a theme, and customized it. We also modified her old Blogger template
to send redirect users to her new blog after displaying a note that the blog had moved. A few years later, we
developed an advertising banner to generate income from her writing.
</p>
<p>
In July 2012, we began hosting the site, as we were already hosting her military wife blog
<a routerLink="/solutions/hard-corps-wife" title="Hard Corps Wife | Bit Badger Solutions">Hard Corps Wife</a>.
When the time came to decommission the site, we backed up the data and ensured she had it.
</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-cassy-fiano',
templateUrl: './cassy-fiano.component.html'
})
export class CassyFianoComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,38 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Dr. Melissa Clouthier (now Mackenzie) blogged from the political right; she also covered health issues and social
media techniques and utilization.
</p>
<h3>The Problem</h3>
<p>
She had seen our work with <a routerLink="/solutions/cassy-fiano"
title="Cassy Fiano | Bit Badger Solutions">Cassy</a>&rsquo;s site, also wanted to move off Blogger; however, she
did not want to lose her years of posts up to that point.
</p>
<h3>The Solution</h3>
<p>
We created a custom theme for her site, imported the content into a WordPress site, and created a specialized
front-page template. She obtained hosting elsewhere; Bit Badger Solutions maintained it there.
</p>
<p>
<small><em>(NOTE: The thumbnail of the site represents a new skin on the original theme; while the theme is the
same, Bit Badger Solutions did not create the graphics.)</em></small>
</p>
<h3>The Epilogue</h3>
<p>Melissa decommissioned this site in 2018; we took final snapshots of the data before shutting it down.</p>
<app-hide-section heading="The Process">
<p>
Initially, we created the theme based off another well-known blogger's site, which had been developed by one of
WordPress's core contributors. We also advised on the type of hosting she would need for her site, and moved
seveal domains there. We also took care of regular backups of her data.
</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-dr-melissa-clouthier',
templateUrl: './dr-melissa-clouthier.component.html'
})
export class DrMelissaClouthierComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,44 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Emerald Mountain Christian School is a private Christian school founded over 50 years ago. They use the Principle
Approach&reg;, which emphasizes research, reasoning, relating, and recording to help students synthesize the
information they learn, rather than just requiring rote memorization. More information about the school&rsquo;s
rich history can be found on their site.
</p>
<h3>The Problem</h3>
<p>
They had a website with very basic information and very little styling; they also had no way of updating it.
</p>
<h3>The Solution</h3>
<p>
In 2004, we developed a theme that brought it in line with the design of their printed materials, adding the
school calendar of events and the entirety of their Parent Information Packet, giving prospective families the
information the needed to determine if the school was a good fit for their students.
</p>
<h3>The Epilogue</h3>
<p>
In 2013, we passed off the content and hosting of the site to a new maintainer. They have since redesigned it;
it is accessible via the URL above, and at <a href="http://emcspatriots.org"
title="EMCS Patriots">EMCSpatriots.org</a>.
</p>
<app-hide-section heading="The Process">
<p>
Initially, we downloaded the content from their old site, and put it into a custom PHP-based framework. We
then added a database of events, and a calendar page that read that database, enabling us to display multiple
years, as well as future and past years. The design of the online information packet looked like a tabbed
notebook, with each page highlighting a different tab.
</p>
<p>
In 2011, we switched the site to use ASP.NET MVC instead of the custom PHP solution, and migrated the data from
MySQL to PostgreSQL; these efforts increased the performance of the site.
</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-emerald-mountain-christian-school',
templateUrl: './emerald-mountain-christian-school.component.html'
})
export class EmeraldMountainChristianSchoolComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,39 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Futility Closet exists as a place to give people a break from the dullness of work, by providing puzzles,
anecdotes, and more. They also publish a weekly podcast highlighting &ldquo;forgotten stories from the pages of
history,&rdquo; along with story updates and lateral thinking puzzles.
</p>
<h3>The Problem</h3>
<p>
The site was running on a shared host, but was growing too large for that platform. The site had also suffered
regular security breaches.
</p>
<h3>The Solution</h3>
<p>
We architected an environment that would support a Reddit or Slashdot deluge of requests, and moved the site to
an implementation of that environment. We continue to maintain that environment and back up data and files for
the over 10,000 posts.
</p>
<app-quotes [quotes]="app.quotes"></app-quotes>
<app-hide-section heading="The Process">
<p>
In mid-2010, we obtained a backup of the previous site, and looked through it to ensure that none of the
breaches had made any permanent changes to the site's structure and data. We also locked down the new server
(hosted on Rackspace Cloud) to only required protocols, training the client on SSH so that they could have
access. We also stood up nginx as the front-end server, boosting performance significantly while requiring a
much smaller server.
</p>
<p>
In 2015, we began hosting Futility Closet (using Digital Ocean).
</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-futility-closet',
templateUrl: './futility-closet.component.html'
})
export class FutilityClosetComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,23 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Our existing client <a routerLink="/solutions/cassy-fiano"
title="Cassy Fiano | Bit Badger Solutions">Cassy Fiano</a>
</p>
<h3>The Problem</h3>
<p>Cassy (now Chesser) wanted a separate place from which to chronicle her experience as a military spouse.</p>
<h3>The Solution</h3>
<p>
In mid-2010, we set up her domain name, created a WordPress site, and customized the header and sidebar for her
selected theme. We also hosted and maintained the site for the duration of its run.
</p>
<h3>The Epilogue</h3>
<p>In 2013, Cassy shifted priorities and closed this site down.</p>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-hard-corps-wife',
templateUrl: './hard-corps-wife.component.html'
})
export class HardCorpsWifeComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,43 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
<a routerLink="/solutions/dr-melissa-clouthier">Melissa Clouthier</a>, Bill Dupray, and Clyde Middleton, all
established conservative bloggers, started a joint venture called <em>Liberty Pundits</em>.
</p>
<h3>The Problem</h3>
<p>
Bill and Clyde had a significant amount of content on a prior site. As they were starting this with established
authors, they needed a site that would handle their expected traffic spikes on popular posts.
</p>
<h3>The Solution</h3>
<p>
In early 2010, we migrated their content from a custom solution into WordPress's database; we then set them up on
the same host where their podcast was being distributed. However, the combination of theme complexity and traffic
overwhelmed that server, so we configured a standalone server with more memory and more efficient software; this
allowed them to routinely eclipse 100,000 views per day, most of those coming on posts within the first few
hours.
</p>
<h3>The Epilogue</h3>
<p>
The site closed in late 2011, as its authors closed their joint venture and moved on to other sites and topics.
</p>
<app-hide-section heading="The Process">
<p>
Before we could migrate the data from <em>Patriot Room</em>, Bill and Clyde's prior home, we had to get into
the server and determine how data was stored in the custom solution. Once we identified where all the data was,
we wrote a custom migration script to shape the data the way WordPress needed it.
</p>
<p>
Bit Badger Solutions maintained the server, keeping it current with performance and security upgrades. We also
provided support to the primary 3 bloggers, when they had questions about WordPress or how the site was
performing.
</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-liberty-pundits',
templateUrl: './liberty-pundits.component.html'
})
export class LibertyPunditsComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,43 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Mindy Mackenzie, the prior Chief Performance Officer of Beam, Inc., is known as the &ldquo;Velvet Hammer&rdquo;
for her tough-yet-caring style of leadership. She is a <em>Wall Street Journal</em> best-selling author of the
book <em>The Courage Solution: The Power of Truth-Telling with Your Boss, Peers, and Team</em>, and the creator
and host of the annual <em>You First Integrative Leadership Summit</em>, equipping women of influence to reach
even greater heights.
</p>
<h3>The Problem</h3>
<p>
Mindy was dissatisfied with the value she was receiving with her current web designer and host; in advance of
her book launch, she needed a more
</p>
<h3>The Solution</h3>
<p>
We took over hosting her site, updating it regularly for the book launch, and highlighting her media appearances
in conjunction with that launch. We also created and continue to maintain the pages for her <em>You First
Integrative Leadership Summit</em>, including online registration.
</p>
<app-quotes [quotes]="app.quotes"></app-quotes>
<app-hide-section heading="The Process">
<p>
In late 2015, We assumed maintenance of her site several months in advance of the book launch. We created a
custom WordPress type to highlight her Media Appearances, automatically ordered from most recent to older. She
had a lot of short video content, and we implemented code that displays a different video each week on the
front page.
</p>
<p>
In early 2018, we developed the pages for her <em>You First Integrative Leadership Summit</em>, with speaker
bios, conference schedule, and an application form. We have continued to maintain these pages across the 2019
and 2020 summits.
</p>
<p>We continue to provide backups, WordPress support, and content updates for Mindy&rsquo;s site.</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-mindy-mackenzie',
templateUrl: './mindy-mackenzie.component.html'
})
export class MindyMackenzieComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,36 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Problem</h3>
<p>
Daniel wanted to maintain a prayer journal, where he could record the prayer requests for which he had prayed,
and the answer that eventually came to that request. He didn't want to do that on paper for several reasons
&ndash; it's easy to lose, a long-running request can run out of space to make notes, etc.
</p>
<h3>The Solution</h3>
<p>
We created a site where users can enter requests, pray through lists of these requests, make notes on them, and
follow them through until they are answered. The site stores no identifying information, and works well on both
desktop and mobile. Bit Badger Solutions hosts and maintains the instance of the site linked above.
</p>
<app-hide-section heading="The Process">
<p>
Development of myPrayerJournal began in earnest in early 2017. As we were using this to learn new techniques,
we ended up trying a host of different front and back end technologies before settling on Vue.js for the front
end and Giraffe for the back end. This combination works well, and we wrote up an 8-post series entitled
<a href="https://blog.bitbadger.solutions/2018/a-tour-of-myprayerjournal/introduction.html">"A Tour of
myPrayerJournal"</a> over on the <em>Bit Badger Blog</em> that steps through all aspects of version 1 of this
application.
</p>
<p>
Version 2 changed to a Material Design interface, and we changed the data store from PostgreSQL to RavenDB, an
excellent document database. As this is an open-source project, anyone can review the source code on
<a href="https://github.com/bit-badger/myPrayerJournal">GitHub</a>; we also track open issues there.
</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-my-prayer-journal',
templateUrl: './my-prayer-journal.component.html'
})
export class MyPrayerJournalComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,47 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
In January 2008, a few members of <a href="http://hoffmantown.org" title="Hoffmantown Church">Hoffmantown
Church</a> in Albuquerque, New Mexico had an idea. The ABC show
<em><a href="http://abc.go.com/shows/extreme-makeover-home-edition">Extreme Makeover: Home Edition</a></em> had
just done <a href="http://abc.go.com/shows/extreme-makeover-home-edition/episode-detail/martinez-family/224884"
title="Martinez Family &bull; Extreme Makeover: Home Edition">a build for a pastor in the &ldquo;war zone&rdquo;
area of town</a>, and this brought attention to Gerald Martinez and the work he had done to help clean up this
area of town. Through <a href="http://www.loveincabq.org/" title="Love INC of South Albuquerque">Love INC of
South Albuquerque</a>, they learned that there were many other homes in that area that could use the &ldquo;Ty
Pennington touch.&rdquo; While the goal was not to knock down homes and build new ones, the goal was no less
extreme. The goal of the &ldquo;Not So Extreme Makeover: Community Edition&rdquo; was to help 50 families in 5
days during spring break week in 2008.
</p>
<h3>The Problem</h3>
<p>
An effort of this magnitude, happening this quickly, would be unmanageable without software support. It would
also require a lot of paperwork, and a lot of people processing that paperwork.
</p>
<h3>The Solution</h3>
<p>
We obtained the domain name and stood up the public website quickly using WordPress, which also allowed the
coordinators to put content up. We then developed an application (NSXapp) where volunteers could sign up for
&ldquo;X Week&rdquo;, with over 80 different skill, talent, and ability categories. We then created a way to
identify families and their needs, and a place for people with donations to let us know what they would be. From
there, we created the ability to begin matching needs with goods (stuff) and abilities (people), organizing the
stuff into donated trailers and people into teams. During X Week, NSXapp generated schedules and reports that
were used to help guide the teams as they executed their projects.
</p>
<h3>The Epilogue</h3>
<p>
From an idea in January, &ldquo;Not So Extreme Makeover: Community Edition&rdquo; was able to help 57 families
by the end of X Week on March 29th. When Love INC saw how NSXapp worked, they expressed an interest in a version
that would allow them to handle these same areas on an ongoing basis; this became
<a routerLink="/solutions/tcms" title="The Clearinghouse Management System | Bit Badger Solutions">TCMS</a>.
Finally, there is a <a href="https://nsx.archive.bitbadger.solutions">snapshot of the NSX public site</a> that
serves as a record of those three months in 2008.
</p>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-nsx',
templateUrl: './nsx.component.html'
})
export class NsxComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,42 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Olivet Baptist Church was a Southern Baptist church in Gulfport, Mississippi, who had seen our work with
<a routerLink="/solutions/bay-vista" title="Bay Vista Baptist Church | Bit Badger Solutions">Bay Vista</a> and
wanted something similar.
</p>
<h3>The Problem</h3>
<p>Olivet had no online presence.</p>
<h3>The Solution</h3>
<p>
Initially, we set up a WordPress site, configured it, and established a podcast feed; we also advised them on how
to register that feed in iTunes. A few years later, we converted the site to behave like an app, where it could
be installed as an icon, allowing quick access.
</p>
<h3>The Epilogue</h3>
<p>
When the church closed its doors on February 24th, 2019, we converted the app-behaving site back to a static web
site, set up an archive site, and worked with their personnel to ensure that the podcast links are all still
available. We continue to host that archive site and podcast content.
</p>
<app-hide-section heading="The Process">
<p>
In 2014, we registered the domain name for the church. They had expressed a desire to do as much of the content
of the site themselves, so we supported them as they worked through its initial setup. After the site was
originally set up, though, updates were rare (apart from the weekly podcast episodes), so we converted it to be
a statically-generated site.
<p>
In 2018, we modified the site to be a Progressive Web Application (PWA), which allows users to
&ldquo;install&rdquo; the site, like an app, to their phone&rsquo;s home screen. The site was also still
accessible from the web via a browser. We converted the static content to generate page fragments that the PWA
would load, providing the same navigation experience as before.
</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-olivet-baptist',
templateUrl: './olivet-baptist.component.html'
})
export class OlivetBaptistComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,33 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Michelle Summers had been photographing her children for years. When her sons were on sports teams, she was
disappointed with the cost of team photography, and felt that she could do a better job at a lower cost. She
specialized in outdoor photography of families, children, and sports teams, as well as maternity photography and
holiday cards.
</p>
<h3>The Problem</h3>
<p>
Michelle needed a site to showcase her previous work, as well as a place to allow her customers to view their
proofs before selecting prints.
</p>
<h3>The Solution</h3>
<p>
We created a WordPress site with image galleries for her existing work, and utilized a custom plug-in to support
online proofs. This site was eventually replaced with one that had a matching Windows application; this
application took a set of photos, resized them, applied a watermark, and created the proof gallery without having
to even go to the site.
</p>
<h3>The Epilogue</h3>
<p>
As Michelle is no longer doing professional photography, the current version of this site is a simple thank-you
to her customers from 2007-2014.
</p>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-photography-by-michelle',
templateUrl: './photography-by-michelle.component.html'
})
export class PhotographyByMichelleComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,45 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Problem</h3>
<p>
Back in 2005, Daniel was responsible for keeping up with prayer requests for his Sunday School class. However,
simply sending out a mass e-mail has some significant drawbacks - everyone's e-mail address is visible to
everyone else; mass e-mails are more likely to be flagged as suspicious; and it is difficult to have a single
&ldquo;latest and greatest&rdquo; list of members.
</p>
<h3>The Solution</h3>
<p>
We wrote a site so we could enter prayer requests and class members; this site would then send individual e-mails
to each member. When requests were 15 days old, they would drop off the list. From there, PrayerTracker has grown
to support multiple churches and groups within those churches, and the user interface is available in both
English <em>y Español</em>. Bit Badger Solutions offers use of this site for free to any church, Sunday School
class, or small group that desires a tool to help them establish a continuous list of prayer requests.
</p>
<app-hide-section heading="The Process">
<p>
The first reimagining of PrayerTracker occurred in 2011; this was when we moved to a more modern (at the time)
framework (ASP MVC 3), building in the multi-church/multi-group security additions, and posturing it for an
interface with <a routerLink="/solutions/virtual-prayer-room"
title="Virtual Prayer Room | Bit Badger Solutions">Virtual Prayer Room</a>. A year later, a visiting missionary
saw the site and liked it, but needed the site (including the online help) in Spanish; we released version 4 a
few months later which brought this support.
</p>
<p>
In late 2014, version 5 moved to a MongoDB data store, as we had some problems with columns not being large
enough for some requests. In early 2017, we released version 6, which took PrayerTracker into the .NET Core
environment; we also moved the data back to PostgreSQL, as it now supported the sizes we needed.
</p>
<p>
Version 7 was released in mid-2018, bringing full mobile accessibility and an upgrade to a modern, ultra-fast
web framework (Giraffe). In early 2019, version 7.1 was the first release for PrayerTracker as an
<a href="https://github.com/bit-badger/PrayerTracker">open source project</a>. Right on its heels, version 7.2
moved the embedded help files to GitHub Pages; this made the web application more streamlined.
</p>
</app-hide-section>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-prayer-tracker',
templateUrl: './prayer-tracker.component.html'
})
export class PrayerTrackerComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,27 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Dan Riehl began blogging as <em>The Carnivorous Conservative</em> back in 2004, specializing in the areas of
crime and politics. He changed to <em>Riehl World View</em> a short time later, and writes both news and opinion
pieces. He was a prolific blogger, publishing over 15 posts a day on most days.
</p>
<h3>The Problem</h3>
<p>
He wanted to take his blog in a different direction, and was having trouble getting his Movable Type blog do move
with him.
</p>
<h3>The Solution</h3>
<p>
We stood up a WordPress site on a server he procured. We then assisted him in selecting a theme and customized it
to his liking. Finally, we wrote custom migration code to get his past body of work into the new site. In 2018,
we generated static files for most of his prior posts, to give him a clean slate for a new direction. We continue
to maintain and support <em>Riehl World News</em>.
</p>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-riehl-world-news',
templateUrl: './riehl-world-news.component.html'
})
export class RiehlWorldNewsComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,27 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
<em>The Shark Tank</em> is a news and opinion site centered on south Florida politics (and the state at large).
They provided extensive coverage of Rep. Allen West&rsquo;s winning campaign in 2010, and continue their focused
news and opinion on current political races.
</p>
<h3>The Problem</h3>
<p>
They were displeased with their current theme; it was struggling with the amount of content they were producing.
</p>
<h3>The Solution</h3>
<p>
They had identified a theme that would better suit their needs. We set it up, ensuring that their content would
fit in the new theme&rsquo;s requirements, and helped them turn off parts that they didn&rsquo;t need. We also
converted the social media connections from their old site to a style that would work nicely in the new theme.
</p>
<h3>The Epilogue</h3>
<p>This was all they needed; they returned their focus to their writing.</p>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-shark-tank',
templateUrl: './shark-tank.component.html'
})
export class SharkTankComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,35 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Love INC of South Albuquerque runs a &ldquo;needs clearinghouse&rdquo;; they have volunteers who accept
donations, and people contact them with their needs. They are then able to match the person who needs something
with that thing, or with someone who can assist them.
</p>
<h3>The Problem</h3>
<p>
The files in their offices were multiplying; ensuring people&rsquo;s needs are not missed, while ensuring that
their clients were not taking advantage of their services, required a lot of paper. They were tracking volunteers
on a spreadsheet, but their contact info was in yet another file. Having worked with us on the
<a routerLink="/solutions/nsx"
title="Not So Extreme Makeover: Community Edition | Bit Badger Solutions">&ldquo;Not So Extreme Makeover:
Community Edition&rdquo;</a>, and thought that the solution we developed for that project would help them.
</p>
<h3>The Solution</h3>
<p>
We adapted NSXapp to handle an ongoing stream of people, volunteers, and donations. This enabled them to spend
more time with the people who needed help. The WordPress front end also served as their public website, and
allowed them to manage the volunteers who were using the system.
</p>
<h3>The Epilogue</h3>
<p>
Love INC of South Albuquerque found a SalesForce system that would do things very similar to TCMS, and was able
to get in on a program that let them use it at no cost; TCMS was decommissioned in 2014.
</p>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-tcms',
templateUrl: './tcms.component.html'
})
export class TcmsComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,37 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<article class="content">
<app-application-image [app]="app"></app-application-image>
<h3>The Client</h3>
<p>
Our existing client <a href="http://hoffmantown.org" title="Hoffmantown Church">Hoffmantown Church</a> in
Albuquerque, New Mexico, with whom we had worked on the <a routerLink="/solutions/nsx/"
title="Not So Extreme Makeover: Community Edition | Bit Badger Solutions">Not So Extreme Makeover: Community
Edition</a>
</p>
<h3>The Problem</h3>
<p>
Hoffmantown had seen the use of this physical prayer room dwindling over the years. People had become less
willing to drive to the church, especially at night, and security became an issue as well; either prayer warriors
had to know how to disable the security system, or the church would have to remain unlocked.
</p>
<h3>The Solution</h3>
<p>
The development of Virtual Prayer Room extended the prayer room to anywhere a prayer warrior can get an Internet
connection! Prayer warriors could enlist right from the site, and had to be approved. Requests and updates were
tracked by date/time, and warriors could record when they&rsquo;ve prayed for a request from the site, or from
clicking a link in the daily e-mail they received with requests from their interest areas. As many prayer needs
are confidential, security and confidentiality were very important. Virtual Prayer Room ensured these by
providing varying security levels for prayer warriors and the ability to mark each request as confidential.
</p>
<h3>The Epilogue</h3>
<p>
In 2016, Hoffmantown Church elected to begin using another package for their prayer requests. While a few other
churches had expressed interest in it, none ultimately decided to use it; so, in 2017, Virtual Prayer Room was
officially decommissioned.
</p>
<app-tech-stack [stack]="app.techStack"></app-tech-stack>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core';
import { AppDetailComponent } from './app-detail.component';
import { App } from '../application.types';
@Component({
selector: 'app-virtual-prayer-room',
templateUrl: './virtual-prayer-room.component.html'
})
export class VirtualPrayerRoomComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,16 @@
<div *ngIf="(stack || []).length > 0">
<app-hide-section heading="The Technology Stack">
<div *ngIf="currentStack.length > 0">
<p><small><strong>Current:</strong></small></p>
<ul>
<app-technology *ngFor="let tech of currentStack" [tech]="tech"></app-technology>
</ul>
</div>
<div *ngIf="priorStack.length > 0">
<p *ngIf="currentStack.length > 0"><small><strong>Previously:</strong></small></p>
<ul>
<app-technology *ngFor="let tech of priorStack" [tech]="tech"></app-technology>
</ul>
</div>
</app-hide-section>
</div>

View File

@ -0,0 +1,4 @@
p
margin-bottom: 0
ul
margin-top: 0

View File

@ -0,0 +1,28 @@
import { Component, OnInit, Input } from '@angular/core'
import { Technology } from '../application.types'
@Component({
selector: 'app-tech-stack',
templateUrl: './tech-stack.component.html',
styleUrls: ['./tech-stack.component.sass']
})
export class TechStackComponent implements OnInit {
@Input() stack: Technology[]
constructor() { }
ngOnInit() { }
/** The currently-used technologies */
get currentStack() {
return this.stack.filter(p => p.current)
}
/** The previously-used technologies */
get priorStack() {
return this.stack.filter(p => !p.current)
}
}

View File

@ -0,0 +1,5 @@
<li>
<a *ngIf="hasLink(tech.name)" [href]="techLinks[tech.name]" target="_blank">{{ tech.name }}</a>
<span *ngIf="!hasLink(tech.name)">{{ tech.name }}</span>
for {{ tech.usedFor }}
</li>

View File

@ -0,0 +1,51 @@
import { Component, OnInit, Input } from '@angular/core'
import { Technology } from '../application.types'
@Component({
selector: 'app-technology',
templateUrl: './technology.component.html',
styleUrls: ['./technology.component.sass']
})
export class TechnologyComponent implements OnInit {
@Input() tech: Technology
/** External links to technology products */
private techLinks = {
'ASP.NET MVC': 'https://dotnet.microsoft.com/apps/aspnet/mvc',
Azure: 'https://azure.microsoft.com/',
'BlogEngine.NET': 'http://www.dotnetblogengine.net/',
'Database Abstraction': 'https://github.com/danieljsummers/DatabaseAbstraction',
'Digital Ocean': 'https://www.digitalocean.com/',
Giraffe: 'https://github.com/giraffe-fsharp/Giraffe',
GitHub: 'https://github.com/',
'GitHub Pages': 'https://pages.github.com/',
Hexo: 'https://hexo.io/',
Hugo: 'https://gohugo.io/',
Jekyll: 'https://jekyllrb.com/',
MongoDB: 'https://www.mongodb.com/',
MySQL: 'https://www.mysql.com/',
myWebLog: 'https://github.com/bit-badger/myWebLog',
nginx: 'http://nginx.org/',
Orchard: 'https://orchardproject.net/',
PHP: 'https://www.php.net/',
PostgreSQL: 'https://www.postgresql.org/',
'Rackspace Cloud': 'https://www.rackspace.com/cloud',
RavenDB: 'https://ravendb.net/',
RethinkDB: 'https://rethinkdb.com/',
'SQL Server': 'https://www.microsoft.com/en-us/sql-server/',
'Vue.js': 'https://vuejs.org/',
WordPress: 'https://wordpress.org'
}
constructor() { }
ngOnInit() { }
/** Whether there is a link for a given product */
hasLink(product: string) {
return this.techLinks[product] !== undefined
}
}

View File

@ -0,0 +1,95 @@
<app-page-title title="Information Publicizing Solutions"></app-page-title>
<article class="content auto">
<h1>Information Publicizing and Blogging</h1>
<p>
In the early days of the World Wide Web, it was known as the "information superhighway." From its inception, the
web's primary goal is information. The open nature of the Internet allows anyone, anywhere to say anything,
provided they can connect a machine to the network. In fact, there are software products to handle everything
except creating the content; all you have to bring is the ability to form a coherent thought, and type that
thought into a box. <a href="https://wordpress.org" title="WordPress">WordPress</a> is one of the most popular
<abbr title="Web Log">blog</abbr>ging platforms in use today; it allows authors to concentrate on the content of
their websites, rather than forcing authors to turn into programmers.
</p>
<h2>Custom-Built Sites</h2>
<ul>
<li>
<p>
We developed and maintained the site for <a href="http://www.emeraldmountainchristianschool.org">Emerald
Mountain Christian School</a>
<small> (<a routerLink="/solutions/emerald-mountain-christian-school" title="Emerald Mountain Christian School | Bit Badger Solutions">about</a>)</small>
for 9 years, where they had information about the type of curriculum they teach, the school's 40+-year history,
a calendar of events, and how to get more information.
</p>
</li>
<li>
<p>
We built and maintained the site for <a href="https://www.summershome.org">Photography by Michelle</a>
<small> (<a routerLink="/solutions/photography-by-michelle" title="Photography by Michelle | Bit Badger Solutions">about</a>)</small>,
which had information, prices, and samples of the photographer's work, as well as the ability for customers to
view proofs and make photo selections online.
</p>
</li>
<li>
<p>
The site for <a href="https://bayvista.org" title="Bay Vista Baptist Church">Bay Vista Baptist Church</a>
<small> (<a routerLink="/solutions/bay-vista" title="Bay Vista Baptist Church | Bit Badger Solutions">about</a>)</small>
utilizes a "static site generator," where the entire site is generated from source files, then served. It
requires no back-end database, which means that the server can send pages as fast as its clients can take them.
This site even has a generated podcast feed! Adding content to these types of sites requires a bit more
technical knowledge beyond "typing text in a box," but it is a great way to build ultra-fast, scalable web
sites.
</p>
</li>
<li>
<p>
This site is a single-page application (SPA) utilizing the <a href="https://angular.io">Angular</a> JavaScript
framework. The application pages are generated based on an internal data set, and the other pages are simple
text components. Its bundling means that the initial page is small, and after the initial load, it runs
entirely in the browser or on a phone or tablet. Sites that reference external data sets would still need to
access the Internet to retrieve data, but this is much more efficient than having to download the entire page
every single click. (It's even <a href="https://github.com/bit-badger/bitbadger.solutions">open source</a> if
you want to see how we did it.)
</p>
</li>
</ul>
<h2>WordPress Design, Customization, and Support</h2>
<ul>
<li>
<p>
We helped <a routerLink="/solutions/cassy-fiano" title="Cassy Fiano | Bit Badger Solutions">Cassy Fiano</a>
and <a routerLink="/solutions/dr-melissa-clouthier" title="Dr. Melissa Clouthier | Bit Badger Solutions">Dr.
Melissa Clouthier</a> both move their blogs from Blogspot to their own domains.
</p>
</li>
<li>
<p>
We migrated <a routerLink="/solutions/liberty-pundits" title="Liberty Pundits">Liberty Pundits</a> from a
custom blog platform to WordPress, and set up and maintained their server, which routinely cleared 100,000
hits per day in its prime.
</p>
</li>
<li>
<p>
For <a href="https://www.futilitycloset.com" title="Futility Closet">Futility Closet</a>
<small> (<a routerLink="/solutions/futility-closet" title="Futility Closet | Bit Badger Solutions">about</a>)</small>,
we moved their site from a shared hosting platform to its own <abbr title="Virtual Private Server">VPS</abbr>,
to enable it to handle its ever-increasing traffic.
</p>
</li>
<li>
<p>
<a routerLink="/solutions/tcms" title="TCMS | Bit Badger Solutions">TCMS</a> and
<a routerLink="/solutions/nsx" title="NSXapp | Bit Badger Solutions">NSXapp</a> both used WordPress as their
front end, which also provided a public web presence that the customers could update themselves.
</p>
</li>
</ul>
<p>
On <em><a href="https://blog.bitbadger.solutions" title="The Bit Badger Blog">The Bit Badger Blog</a></em> you can
browse the
<a href="https://blog.bitbadger.solutions/category/wordpress" title="WordPress | The Bit Badger Blog">WordPress</a>
category for information on plug-ins, and we have supported theme customizations for nearly all of the WordPress
sites linked on the sidebar/footer of the home page.
</p>
<p><br><a routerLink="/" title="Home">&laquo; Home</a></p>
</article>

View File

@ -0,0 +1,13 @@
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-information-publicizing',
templateUrl: './information-publicizing.component.html'
})
export class InformationPublicizingComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -1,8 +1,7 @@
<template lang="pug"> <app-page-title title="Legacy Data Solutions"></app-page-title>
article.content.auto <article class="content auto">
page-title(title='Legacy Data Solutions') <h1>Legacy Data Sharing</h1>
h1 Legacy Data Sharing <p>
p.
Our background in mainframe applications gives us a knowledgeable perspective on retrieving information from Our background in mainframe applications gives us a knowledgeable perspective on retrieving information from
older, &ldquo;legacy&rdquo; systems. This data can be migrated to a more modern relational or document database, older, &ldquo;legacy&rdquo; systems. This data can be migrated to a more modern relational or document database,
where a web application can retrieve the information; in some cases, the data can even be exposed as a web service where a web application can retrieve the information; in some cases, the data can even be exposed as a web service
@ -10,7 +9,6 @@ article.content.auto
without having to move their day-to-day system from its current environment. While we currently have no active without having to move their day-to-day system from its current environment. While we currently have no active
projects along these lines, our developers have done them in the past for other organizations; sadly, none can be projects along these lines, our developers have done them in the past for other organizations; sadly, none can be
linked publicly. linked publicly.
p </p>
br <p><br><a routerLink="/" title="Home">&laquo; Home</a></p>
router-link(to='/' title='Home') &laquo; Home </article>
</template>

View File

@ -0,0 +1,13 @@
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-legacy-data',
templateUrl: './legacy-data.component.html'
})
export class LegacyDataComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -0,0 +1,45 @@
<app-page-title title="Process Automation Solutions"></app-page-title>
<article class="content auto">
<h1>Process Automation and User Engagement</h1>
<p>
Computers can be used to augment or automate nearly any process; could you think of generating bank statements,
processing mailing lists, or tracking orders without some form of automation? We develop web-based solutions to
automate <em>your</em> processes, ensuring that your business constraints are satisfied; these systems can run on
the Internet or your private network. For Internet-facing solutions, we engineer solutions that allow them to
interact with you securely, presented in an engaging manner. And, by &ldquo;engagement,&rdquo; we are not
describing intrusive page pop-ups and other <span class="strike">marketing gimmicks</span> web annoyances; we
determine an optimal user experience for <em>your</em> customers, and tailor the solution to work for both of you.
</p>
<p>Several of our solutions fit this description.</p>
<ul>
<li>
<p>
<a routerLink="/solutions/virtual-prayer-room" title="Virtual Prayer Room | Bit Badger Solutions">Virtual
Prayer Room</a> helped the prayer ministry of
<a href="http://www.hoffmantown.org" title="Hoffmantown Church in Albuquerque, NM">Hoffmantown Church</a>
enable their prayer warriors to have access to requests wherever they are, even in their inbox once a day!
</p>
</li>
<li>
<p>
<a routerLink="/solutions/tcms" title="TCMS | Bit Badger Solutions">TCMS</a> was an application that helped
organizations such as <a href="http://www.loveincabq.org">Love INC of South Albuquerque</a> connect people with
needs to people who can help fulfill those needs. TCMS sprung from the
<a href="https://nsx.archive.bitbadger.solutions" title="Not So Extreme Makeover: Community Edition (Archive)">Not
So Extreme Makeover: Community Edition</a> in Albuquerque, New Mexico during spring break 2008; we not only
developed the public presence, but a private system called
<a routerLink="/solutions/nsx" title="NSXapp | Bit Badger Solutions">NSXapp</a> that enabled the management of
the volunteers, families, and things for this massive effort.
</p>
</li>
<li>
<p>
We continue to offer <a href="https://prayer.bitbadger.solutions" title="PrayerTracker">PrayerTracker</a>
<small> (<a routerLink="/solutions/prayer-tracker" title="PrayerTracker | Bit Badger Solutions">about</a>)</small>,
a free-to-use web application that helps Sunday School classes (or other small groups) generate a prayer
request list; it provides a central place for list management and continuity.
</p>
</li>
</ul>
<p><br><a routerLink="/" title="Home">&laquo; Home</a></p>
</article>

View File

@ -0,0 +1,13 @@
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-process-automation',
templateUrl: './process-automation.component.html'
})
export class ProcessAutomationComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -0,0 +1,45 @@
<app-page-title title="Web Services and API Solutions"></app-page-title>
<article class="content auto">
<h1>Web Services and APIs</h1>
<p>
A web service is a way of using the Internet to provide or accept information that makes sense to computers; this
allows other sites or applications to consume information from, or provide information to, your service. This
enables communication between applications, without having to establish any communication channels other than the
ones that web browsers already use. It isn't the best fit for every application, but when it is useful, it is
<em>very</em> useful.
</p>
<p>
An <abbr title="Application Programming Interface">API</abbr> can be a synonym for a web service, but it can also
be a generally accessible way of providing data. For example, Twitter has a public API, which other applications
can use to display tweets on their site.
</p>
<ul>
<li>
<p>
<a routerLink="/solutions/photography-by-michelle" title="Photography by Michelle | Bit Badger Solutions">Photography
by Michelle</a> had a private web API that a desktop application utilized to create the online proof sets right
from the computer where the images resided.
</p>
</li>
<li>
<p>
We
<a href="https://blog.bitbadger.solutions/2010/4040-web-service.html" title="40/40 Web Service | The Bit Badger Blog">wrote
a service</a> for the 2010
<a href="http://erlc.com/4040/" title="40/40 Prayer Vigil | Ethics and Religious Liberty Commission of the Southern Baptist Convention">40/40
Prayer Vigil</a>, which was utilized by several sites to display the current day's (or hour's) prayer focus, and
<a href="https://blog.bitbadger.solutions/2012/4040-web-service-for-2012.html" title="40/40 Web Service for 2012 | The Bit Badger Blog">wrote
one for 2012</a> as well. <em>(As the ERLC does not host these any more, this service is no longer active.)</em>
</p>
</li>
<li>
<p>
<a href="https://prayerjournal.me">myPrayerJournal</a>
<small> (<a routerLink="/solutions/my-prayer-journal" title="myPrayerJournal | Bit Badger Solutions">about</a>)</small>
is a <abbr title="Single Page Application">SPA</abbr> which only downloads the structure of the site the first
time you go there, then utilizes a stateless API to access data from the browser.
</p>
</li>
</ul>
<p><br><a routerLink="/" title="Home">&laquo; Home</a></p>
</article>

View File

@ -0,0 +1,13 @@
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-web-services',
templateUrl: './web-services.component.html'
})
export class WebServicesComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -1,23 +1,24 @@
<template lang="pug"> <app-page-title title="Why Bit Badger?"></app-page-title>
article.content.auto <article class="content auto">
page-title(title='Why Bit Badger?') <h1>Why &ldquo;Bit Badger&rdquo;?</h1>
h1 Why &ldquo;Bit Badger&rdquo;? <p>
p.
A while back, our primary developer Daniel learned through genetic testing that he had one gene that was not right A while back, our primary developer Daniel learned through genetic testing that he had one gene that was not right
(technically known as a genetic mutation). He is currently fine #[em (thank you for asking)], but his co-workers (technically known as a genetic mutation). He is currently fine <em>(thank you for asking)</em>, but his co-workers
thought of another group of genetic mutants &ndash; the X-Men. They wanted to develop the mutant identity for him thought of another group of genetic mutants &ndash; the X-Men. They wanted to develop the mutant identity for him
in that style; since Wolverine is already taken, they wanted something similar, but based on a member of the weasel in that style; since Wolverine is already taken, they wanted something similar, but based on a member of the weasel
family (for its normal private life and fierce tenacity, not its morals). They went through several different family (for its normal private life and fierce tenacity, not its morals). They went through several different
options, but when &ldquo;Bit Badger&rdquo; was mentioned, it was the winner. The Bit Badger's mutant superpower is options, but when &ldquo;Bit Badger&rdquo; was mentioned, it was the winner. The Bit Badger's mutant superpower is
the ability to shoot 1s and 0s out its nostrils! the ability to shoot 1s and 0s out its nostrils!
p. </p>
<p>
Daniel liked this moniker, and decided to run with it. He had been growing dissatisfied with the name &ldquo;DJS Daniel liked this moniker, and decided to run with it. He had been growing dissatisfied with the name &ldquo;DJS
Consulting,&rdquo; as he felt that name was passive. He enjoys taking problems and finding creative solutions for Consulting,&rdquo; as he felt that name was passive. He enjoys taking problems and finding creative solutions for
them, making our computers work for us instead of the other way around. While he can't actually breathe out 1s and them, making our computers work for us instead of the other way around. While he can't actually breathe out 1s and
0s, they do flow from his fingers (in groups of 8, of course). 0s, they do flow from his fingers (in groups of 8, of course).
p. </p>
Do you have a problem that needs a solution? #[a(href='mailto:daniel@bitbadger.solutions') Sic the Bit Badger on it]! <p>
p Do you have a problem that needs a solution? <a href="mailto:daniel@bitbadger.solutions">Sic the Bit Badger on
br it</a>!
router-link(to='/' title='Home') &laquo; Home </p>
</template> <p><br><a routerLink="/" title="Home">&laquo; Home</a></p>
</article>

View File

@ -0,0 +1,13 @@
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-why-bit-badger',
templateUrl: './why-bit-badger.component.html'
})
export class WhyBitBadgerComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -0,0 +1,60 @@
<app-page-title title="Welcome!"></app-page-title>
<div class="home">
<article class="content auto">
<p class="home-lead">Bit Badger Solutions develops the site you need to enable your success!</p>
<p>These solutions can take several different forms.</p>
<h2>Process Automation and User Engagement</h2>
<p>
Do you have a process that requires recording the same thing multiple times? Do you have information in different
places, but you need it all together? This solution is for you.
<a routerLink="/about/process-automation-solutions" title="Process Automation Solutions">Learn more about how
our solutions automate processes and engage users</a>.
</p>
<h2>Information Publicizing and Blogging</h2>
<p>
From its inception, the Web has been about information. Do you need to get information out about an upcoming
event? Are you wanting to start blogging, or breathe some fresh life into an existing blog? Those are but a few
of the problems that this solution solves.
<a routerLink="/about/information-publicizing-solutions" title="Information Publicizing Solutions">Find out more
about our information publicizing and blogging solutions</a> (including WordPress and statically-generated
sites).
</p>
<h2>Web Services and APIs</h2>
<p>
Do you have a need for multiple computers to talk to each other? Do you have an interesting data set that you want
to make available to the public? A web service or API may be just the solution for you.
<a routerLink="/about/web-services-solutions" title="Web Services and API Solutions">Learn about web services,
along with examples of current solutions</a>.
</p>
<h2>Legacy Data Sharing</h2>
<p>
Do you have data that's old &mdash; and by &ldquo;old,&rdquo; we aren&rsquo;t talking &ldquo;iPhone 6&rdquo; old,
we&rsquo;re talking &ldquo;this data
<a href="https://en.wikipedia.org/wiki/Age_of_candidacy#United_States" title="Age of Candidacy (United States) | Wikipedia">could
run for President</a>&rdquo; old? Just because the information is in an older &ldquo;legacy&rdquo; system
doesn&rsquo;t mean it has to stay there.
<a routerLink="/about/legacy-data" title="Legacy Data Sharing Solutions">Learn how our solutions can help get
this data where you and your customers can access it more easily</a>.
</p>
<h2>Why Web-Based?</h2>
<p>Web-based solutions have many advantages:</p>
<ul>
<li><p>They can be used just on a local, private network (an intranet) or on the public Internet.</p></li>
<li><p>They are available to any device connected to the network.</p></li>
<li><p>They require no special software; every device has a browser - which you're using to read this!)</p></li>
<li><p>They can get your most critical needs met first, then evolved and improved over time.</p></li>
</ul>
<h2>What Is a &ldquo;Bit Badger&rdquo;?</h2>
<p>
<a routerLink="/about/why-bit-badger" title="Why Bit Badger?">Read the Bit Badger&rsquo;s origin story</a>.
</p>
<h2>Solutions to Your Problems</h2>
<p>
We&rsquo;d be happy to discuss your information technology needs, and which of our solutions are right for you.
Just <a href="mailto:daniel@bitbadger.solutions">e-mail us</a> and let us know what we can do for you! You can
also <a routerLink="/solutions" title="All Solutions">browse a complete list of our current and previous
solutions</a>.
</p>
</article>
<app-sidebar></app-sidebar>
</div>

View File

@ -0,0 +1,9 @@
@media all and (min-width: 80rem)
.home
display: flex
flex-flow: row
align-items: flex-start
justify-content: space-around
.home-lead
font-size: 1.3rem
text-align: center

View File

@ -0,0 +1,14 @@
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.sass']
})
export class HomeComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -0,0 +1,3 @@
<footer>
A <strong><a routerLink="/">Bit Badger Solutions</a></strong> original design
</footer>

Some files were not shown because too many files have changed in this diff Show More