Initial commit

This commit is contained in:
Fergal Moran
2019-02-06 00:23:20 +00:00
parent 0ed4928d84
commit 2e1eb0b4af
33 changed files with 778 additions and 331 deletions

View File

@@ -4,7 +4,7 @@ root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

8
Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM nginx:alpine
COPY nginx/conf.d /etc/nginx/nginx.conf
WORKDIR /usr/share/nginx/html
COPY dist/app/ .
EXPOSE 80

View File

@@ -29,16 +29,17 @@
"styles": [
"src/styles.scss"
],
"scripts": []
"scripts": [
"node_modules/jquery/dist/jquery.js",
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"fileReplacements": [{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
}],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
@@ -48,13 +49,11 @@
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"budgets": [{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
}]
}
}
},

5
build.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
npm --no-git-tag-version --tag-version-prefix="" version patch
ng build --prod --aot
docker build -t fergalmoran/retns-frontent .
docker push fergalmoran/retns-frontent

36
nginx/conf.d Normal file
View File

@@ -0,0 +1,36 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name retns.fergl.ie;
return 301 http://retns.fergl.ie$request_uri;
}
server {
listen 80;
server_name retns.fergl.ie;
root /usr/share/nginx/html;
index index.html index.htm;
include /etc/nginx/mime.types;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";
gzip on;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
location / {
try_files $uri $uri/ /index.html;
}
}
}

20
package-lock.json generated
View File

@@ -716,6 +716,11 @@
}
}
},
"@fortawesome/fontawesome-free-webfonts": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-webfonts/-/fontawesome-free-webfonts-1.0.9.tgz",
"integrity": "sha512-nLgl6b6a+tXaoJJnSRw0hjN8cWM/Q5DhxKAwI9Xr0AiC43lQ2F98vQ1KLA6kw5OoYeAyisGGqmlwtBj0WqOI5Q=="
},
"@ngtools/webpack": {
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.0.7.tgz",
@@ -1665,6 +1670,11 @@
"multicast-dns-service-types": "^1.1.0"
}
},
"bootstrap": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.2.1.tgz",
"integrity": "sha512-tt/7vIv3Gm2mnd/WeDx36nfGGHleil0Wg8IeB7eMrVkY0fZ5iTaBisSh8oNANc2IBsCc6vCgCNTIM/IEN0+50Q=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -5621,6 +5631,11 @@
"integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=",
"dev": true
},
"jquery": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz",
"integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg=="
},
"js-base64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
@@ -6402,6 +6417,11 @@
"minimist": "0.0.8"
}
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

View File

@@ -20,7 +20,11 @@
"@angular/platform-browser": "~7.0.0",
"@angular/platform-browser-dynamic": "~7.0.0",
"@angular/router": "~7.0.0",
"@fortawesome/fontawesome-free-webfonts": "^1.0.9",
"bootstrap": "^4.2.1",
"core-js": "^2.5.4",
"jquery": "^3.3.1",
"moment": "^2.24.0",
"rxjs": "~6.3.3",
"zone.js": "~0.8.26"
},

View File

@@ -1,21 +1,8 @@
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
</li>
</ul>
<router-outlet></router-outlet>
<app-navbar></app-navbar>
<router-outlet>
<main role="main"
class="container">
<app-home></app-home>
</main>
</router-outlet>
<app-footer></app-footer>

View File

@@ -0,0 +1,3 @@
.container {
padding: 90px 15px 0;
}

View File

@@ -1,17 +1,17 @@
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CurrentWeekComponent } from './shared/current-week/current-week.component';
import { HomeComponent } from './home/home.component';
import { NavbarComponent } from './shared/navbar/navbar.component';
import { FooterComponent } from './shared/footer/footer.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
declarations: [AppComponent, CurrentWeekComponent, HomeComponent, NavbarComponent, FooterComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [],
bootstrap: [AppComponent]
})

View File

@@ -0,0 +1,3 @@
<div class="container">
<app-current-week></app-current-week>
</div>

View File

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

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

View File

@@ -0,0 +1,7 @@
export interface CalendarModel {
id: string;
subjects: string[];
weekCommencing: Date;
notes: string;
days: any[];
}

View File

@@ -0,0 +1,9 @@
<p [innerHTML]="data$ | async"
*ngIf="!this.errorText">
</p>
<div class="alert alert-danger"
role="alert"
*ngIf="this.errorText">
{{this.errorText}}
</div>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CurrentWeekComponent } from './current-week.component';
describe('CurrentWeekComponent', () => {
let component: CurrentWeekComponent;
let fixture: ComponentFixture<CurrentWeekComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CurrentWeekComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CurrentWeekComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,34 @@
import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import { Observable, EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Component({
selector: 'app-current-week',
templateUrl: './current-week.component.html',
styleUrls: ['./current-week.component.scss']
})
export class CurrentWeekComponent implements OnInit {
data$: Observable<string>;
errorText: string = '';
constructor(private httpClient: HttpClient) {}
ngOnInit() {
this.data$ = this.httpClient
.get(`${environment.apiHost}/gethomework`, {
responseType: 'text'
})
.pipe(
catchError((err: HttpErrorResponse) => {
if (err.status === 404) {
this.errorText = 'No homework found!!!!';
} else {
this.errorText = err.statusText;
}
return EMPTY;
})
);
}
}

View File

@@ -0,0 +1,9 @@
<footer class="footer mt-auto py-3">
<div class="container-fluid">
<span class="text-muted">Crafted with <i class="fa fa-heart fa-pulse text-pulse"></i>
by <a class="font-w600"
href="https://podnoms.com"
target="_blank">PodNoms</a>
</span>
</div>
</footer>

View File

@@ -0,0 +1,90 @@
.footer {
position: fixed;
bottom: 0;
width: 100%;
height: 60px;
line-height: 60px;
background-color: #f5f5f5;
}
.text-pulse {
color: #e74c3c !important;
}
.fa-pulse {
display: inline-block;
-moz-animation: pulse 2s infinite linear;
-o-animation: pulse 2s infinite linear;
-webkit-animation: pulse 2s infinite linear;
animation: pulse 2s infinite linear;
}
@-webkit-keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-moz-keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-o-keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-ms-keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.3;
}
100% {
opacity: 1;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FooterComponent } from './footer.component';
describe('FooterComponent', () => {
let component: FooterComponent;
let fixture: ComponentFixture<FooterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FooterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss']
})
export class FooterComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1,14 @@
<nav class="navbar fixed-top shadow">
<a class="navbar-brand col-sm-3 col-md-2 mr-0"
href="#">RETNS Senior
Infants Homework</a>
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link"
href="#"
(click)="addToCalendar()"><i class="fa fa-calendar-plus"></i>&nbsp;Add
To
Calendar</a>
</li>
</ul>
</nav>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NavbarComponent } from './navbar.component';
describe('NavbarComponent', () => {
let component: NavbarComponent;
let fixture: ComponentFixture<NavbarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ NavbarComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NavbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,66 @@
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import { CalendarModel } from 'src/app/models/calendar.model';
import * as moment from 'moment';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
constructor(private httpClient: HttpClient) {}
days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday'];
ngOnInit() {}
addToCalendar() {
if (
confirm(
'This will open 4 new tabs with pre-filled gmail calendar events for each day!'
)
) {
this._addCalendarEvents();
}
}
_addCalendarEvents() {
this.httpClient
.get<CalendarModel>(`${environment.apiHost}/calendar`)
.subscribe((r) => {
for (const [i, d] of this.days.entries()) {
const day = r.days[d];
const description = Object.keys(day)
.map((k) => `${k}: ${day[k]}`)
.join('\n');
console.log('navbar.component', 'Description', description);
const scheduledDate = new Date(
new Date(r.weekCommencing).getTime() +
i * (1000 * 60 * 60 * 24)
);
scheduledDate.setHours(16);
scheduledDate.setMinutes(0);
scheduledDate.setSeconds(0);
const scheduledEndDate = new Date(scheduledDate);
scheduledEndDate.setMinutes(30);
const s = moment(scheduledDate).format('YYYYMMDDTHHmm00');
const e = moment(scheduledEndDate).format('YYYYMMDDTHHmm00');
const url =
`https://calendar.google.com/calendar/r/eventedit` +
`?text=${d}'s Homework` +
`&dates=${s}/${e}` +
`&details=${encodeURIComponent(description)}` +
`&sprop=website:&sf=true`;
console.log('navbar.component', 'URL:', url);
window.open(url, '_blank');
}
// console.log('navbar.component', 'Monday', r.days['Monday']);
// console.log('navbar.component', 'Tuesday', r.days['Tuesday']);
// console.log(
// 'navbar.component',
// 'Wednesday',
// r.days['Wednesday']
// );
// console.log('navbar.component', 'Thursday', r.days['Thursday']);
});
}
}

View File

@@ -3,7 +3,8 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
production: false,
apiHost: 'http://localhost:5000/api'
};
/*

View File

@@ -1,14 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>RetnsFrontend</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<meta name="viewport"
content="width=device-width, initial-scale=1">
<link rel="icon"
type="image/x-icon"
href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -1 +1,16 @@
/* You can add global styles to this file, and also import other style files */
$fa-font-path: '~@fortawesome/fontawesome-free-webfonts/webfonts';
@import '~bootstrap/dist/css/bootstrap.min.css';
@import '~@fortawesome/fontawesome-free-webfonts/scss/fa-regular';
@import '~@fortawesome/fontawesome-free-webfonts/scss/fa-solid';
@import '~@fortawesome/fontawesome-free-webfonts/scss/fa-brands';
@import '~@fortawesome/fontawesome-free-webfonts/scss/fontawesome';
html {
position: relative;
min-height: 100%;
}
body {
/* Margin bottom by footer height */
margin-bottom: 60px;
}

View File

@@ -10,6 +10,7 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"downlevelIteration": true,
"typeRoots": [
"node_modules/@types"
],

View File

@@ -60,7 +60,7 @@
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
false,
"ignore-params"
],
"no-misused-new": true,