diff --git a/.gitignore b/.gitignore index 6193f74..9251fe0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,9 @@ tests/tmp #System files .project +.pydevproject .DS_Store +.vagrant # Other document-it diff --git a/README.md b/README.md index 118b0ca..7f52ab2 100644 --- a/README.md +++ b/README.md @@ -7,62 +7,95 @@ Built using [Codeigniter](http://ellislab.com/codeigniter) ## Team Daniel da Silva - [Github](https://github.com/danielfdsilva) -Ricardo Mestre - [Github](https://github.com/ricardomestre) -Olaf Veerman - [Github](https://github.com/olafveerman) +Ricardo Mestre - [Github](https://github.com/ricardomestre) +Olaf Veerman - [Github](https://github.com/olafveerman) +Nuno Veloso - [Github](https://github.com/nunoveloso) + +___ ## Development environment +To ease development, everything was bundled in a vagrant box. To set it up, simply run ```vagrant up``` from the root of the project. For more details about the Vagrant box and useful commands, check out the [instructions in the wiki](https://github.com/flipside-org/aw-datacollection/wiki/Vagrant-box). + +If you want to develop locally without using the vagrant box, check the [local development section](https://github.com/flipside-org/aw-datacollection/wiki/Local-development) for the needed dependencies. + + ### Requirements +These dependencies are needed to build the app no matter whether you use the Vagrant box or manually set up the environment: + - Node & Npm - Grunt ( $ npm install -g grunt-cli ) - Bower ($ npm install -g bower) +- Sass +- Compass (>= *1.0.0.alpha.19* ```$ sudo gem install compass -v 1.0.0.alpha.19 --pre```) ### Setup -**This is to be done in the local machine, not on vagrant** -After cloning the repository, setup the public files directory and its subfolders at the root of the app: +Initialize and update git submodules: ``` -$ mkdir -p files/surveys -$ chmod -R 777 files +$ git submodule update --init --recursive ``` +Subsequent updates of git submodules must be done without the ```--init``` flag -Update git submodules (**This should be also done after pulling changes**): +Install airwolf dependencies: ``` -$ git submodule update --recursive +$ npm install ``` -The enketo library needs to be built. Change into its directory: ``` -$ cd assets/libs/enketo-core -$ npm install -$ grunt +$ bower install ``` -### Task automation -After these basic requirements are met, run the following commands in the website's folder: +The enketo library needs to be built: ``` +$ cd assets/libs/enketo-core $ npm install - +$ grunt --force ``` +Patch pyxform library, from the app root folder: ``` -$ bower install +$ git apply --directory=application/third_party/pyxform/ pyxform_validate_and_constants.patch +``` + +Build the CSS and Javascript, from the root folder of the + +``` +$ grunt build ``` -Bower will create a ```bower_components``` directory in the src with all the sass and js needed for foundation. Nothing needs to be done there. +___ -You might have to run these as sudo. +## First run +To setup the application go to ```http://your-domain.com/fixtures``` or ```http://192.168.99.10/airwolf/fixtures``` if you're using the vagrant box. + +This will give you 2 options to setup the application: +- **Live** + - All the data present in the application will be removed and a user will be added. + - Default credentials: **admin** | admin +- **Development** + - All the data present in the application will be replaced with dummy data. + - This data includes surveys in various statuses and several users. + - Administrator: **admin** | admin + - Moderator: **moderator** | moderator + - Agent: **agent** | agent + - User with all roles: **all_roles** | all_roles + +After this setup, change the environment to *production* on ```index.php``` +___ + +## Build automation +Grunt is used for the build automation. -#### Getting started ``` $ grunt ``` -Compiles the compass files, javascripts and generates the website. +Compiles the compass files, javascript and generates the website. The system will watch files and execute tasks whenever one of them changes. -#### Other commands +### Other commands Clean the compiled sass and javascript: ``` $ grunt clean ``` -Compile the compass files, javascripts and generate the website. Use this instead of ```grunt``` if you just want to render it once: +Compile the compass files, javascript and generate the website. Use this instead of ```grunt``` if you just want to render it once: ``` $ grunt build ``` @@ -71,37 +104,8 @@ Compile the compass files and javascripts prepared for production (minified, ugl ``` $ grunt prod ``` - -### Running -**Temporary**   -The aw-datacollection app requires mongo.   -Ssh into the vagrant machine and start mongo. -``` -$ vagrant ssh -$ mongod -``` - -## Vagrant machine - -### Requirements -- No requirements (Everything needed is installed during bootstrap) - +___ ## Testing -Testing should be done from within the vagrant machine. - -### Code testing - -Server side testing is being done with phpunit. -Check the documentation for more. - -### Behaviour testing - -This is implemented with [CasperJS](http://casperjs.org/) and currently living under `tests/casperjs`. - -``` -@todo -- integration with grunt -- document usage -``` - +Testing is done using phpunit. To run the tests you just need to run ```phpunit``` in the app's root folder. +If you're using the Vagrant box environment be sure to do this inside the vagrant machine. \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..4255e36 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,121 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| + # All Vagrant configuration is done here. The most common configuration + # options are documented and commented below. For a complete reference, + # please see the online documentation at vagrantup.com. + + # Every Vagrant virtual environment requires a box to build off of. + config.vm.box = "precise32" + + # Base Box url + config.vm.box_url = "http://files.vagrantup.com/precise32.box" + + # Hostname. + config.vm.hostname = "legoBrick" + + # The url from where the 'config.vm.box' box will be fetched if it + # doesn't already exist on the user's system. + # config.vm.box_url = "http://domain.com/path/to/above.box" + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + config.vm.network :forwarded_port, guest: 80, host: 8081 + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + config.vm.network :private_network, ip: "192.168.99.10" + + # Provisioning. + config.vm.provision :shell, :path => "bootstrap.sh" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network :public_network + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + config.vm.synced_folder ".", "/vagrant", owner: "vagrant", group: "www-data", + :mount_options => ['dmode=775', 'fmode=664'] + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + # config.vm.provider :virtualbox do |vb| + # # Don't boot with headless mode + # vb.gui = true + # + # # Use VBoxManage to customize the VM. For example to change memory: + # vb.customize ["modifyvm", :id, "--memory", "1024"] + # end + # + # View the documentation for the provider you're using for more + # information on available options. + + # Enable provisioning with Puppet stand alone. Puppet manifests + # are contained in a directory path relative to this Vagrantfile. + # You will need to create the manifests directory and a manifest in + # the file precise32.pp in the manifests_path directory. + # + # An example Puppet manifest to provision the message of the day: + # + # # group { "puppet": + # # ensure => "present", + # # } + # # + # # File { owner => 0, group => 0, mode => 0644 } + # # + # # file { '/etc/motd': + # # content => "Welcome to your Vagrant-built virtual machine! + # # Managed by Puppet.\n" + # # } + # + # config.vm.provision :puppet do |puppet| + # puppet.manifests_path = "manifests" + # puppet.manifest_file = "init.pp" + # end + + # Enable provisioning with chef solo, specifying a cookbooks path, roles + # path, and data_bags path (all relative to this Vagrantfile), and adding + # some recipes and/or roles. + # + # config.vm.provision :chef_solo do |chef| + # chef.cookbooks_path = "../my-recipes/cookbooks" + # chef.roles_path = "../my-recipes/roles" + # chef.data_bags_path = "../my-recipes/data_bags" + # chef.add_recipe "mysql" + # chef.add_role "web" + # + # # You may also specify custom JSON attributes: + # chef.json = { :mysql_password => "foo" } + # end + + # Enable provisioning with chef server, specifying the chef server URL, + # and the path to the validation key (relative to this Vagrantfile). + # + # The Opscode Platform uses HTTPS. Substitute your organization for + # ORGNAME in the URL and validation key. + # + # If you have your own Chef Server, use the appropriate URL, which may be + # HTTP instead of HTTPS depending on your configuration. Also change the + # validation key to validation.pem. + # + # config.vm.provision :chef_client do |chef| + # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" + # chef.validation_key_path = "ORGNAME-validator.pem" + # end + # + # If you're using the Opscode platform, your validator client is + # ORGNAME-validator, replacing ORGNAME with your organization name. + # + # If you have your own Chef Server, the default validation client name is + # chef-validator, unless you changed the configuration. + # + # chef.validation_client_name = "ORGNAME-validator" +end diff --git a/application/config/config.php b/application/config/config.php index 7fc0ed3..c80f3a4 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -14,7 +14,7 @@ | path to your installation. | */ -$config['base_url'] = 'http://192.168.99.10/work/aw-datacollection/'; +$config['base_url'] = 'http://192.168.99.10/airwolf/'; //$config['base_url'] = ''; /* @@ -393,4 +393,4 @@ $config['aw_admin_email'] = 'aw-datacollection@airwolf.edispilf.org'; /* End of file config.php */ -/* Location: ./application/config/config.php */ \ No newline at end of file +/* Location: ./application/config/config.php */ diff --git a/application/config/mongodb.php b/application/config/mongodb.php index 87c71fc..20debae 100644 --- a/application/config/mongodb.php +++ b/application/config/mongodb.php @@ -5,4 +5,4 @@ $config['default']['persist'] = TRUE; $config['default']['persist_key'] = 'ci_persist'; $config['default']['replica_set'] = FALSE; -$config['default']['query_safety'] = 'safe'; +$config['default']['query_safety'] = 'w'; diff --git a/application/controllers/fixtures.php b/application/controllers/fixtures.php index f14e6a6..ac2d870 100644 --- a/application/controllers/fixtures.php +++ b/application/controllers/fixtures.php @@ -107,22 +107,22 @@ public function index() { show_error('Not allowed. Only available during development'); } - echo '

Fixtures:

'; - echo anchor('fixtures/all', 'Setup fixtures') . '

'; - echo anchor('fixtures/live_setup', 'Live (Only admin user is created)'); + echo '

Setup:

'; + echo anchor('fixtures/all', 'Development') . '

'; + echo anchor('fixtures/live_setup', 'Live'); } /** * Populate db. */ - public function all($key = NULL) { + public function all() { if (ENVIRONMENT != 'development' && ENVIRONMENT != 'demo'){ show_error('Not allowed. Only available during development'); } else if (ENVIRONMENT == 'demo') { - $demo_key = @file_get_contents('reset.demo.key'); + $demo_key = trim(@file_get_contents('reset.demo.key')); - if (!$demo_key || $demo_key != $key) { + if (!$demo_key || $demo_key != $_GET['reset_key']) { show_error('Wrong or missing key.'); } } @@ -150,7 +150,12 @@ public function all($key = NULL) { // Indexes. $this->mongo_db->addIndex('call_tasks', array('ctid' => 'asc')); - redirect('/'); + if (ENVIRONMENT == 'development') { + redirect('/'); + } + else { + print "Done"; + } } } diff --git a/application/views/base/html_end.php b/application/views/base/html_end.php index e51eb46..4c84c95 100644 --- a/application/views/base/html_end.php +++ b/application/views/base/html_end.php @@ -1,5 +1,10 @@ + +

+ View on GitHub +

+ load->view('components/confirm_box') ?> load->view('base/footer_scripts') ?> - \ No newline at end of file + diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 0000000..2a1c620 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +VERSION=2 + +if [ -e runonce.vagrant ]; then + CURRENT_VERSION=$(tail -n 1 runonce.vagrant) + # Just an early check to kill the script. + if (( $CURRENT_VERSION >= $VERSION )); then + echo "Provisioning at latest version. VERSION: $CURRENT_VERSION" + exit; + fi +else + CURRENT_VERSION=0 +fi + +if (( $CURRENT_VERSION < 1 )); then + CURRENT_VERSION=1 + echo "Installing version $CURRENT_VERSION" + ####################################### + ## START PROVISIONING ## + ####################################### + sudo apt-get update + + # General Program + sudo apt-get -y install vim + sudo apt-get -y install unzip + sudo apt-get -y install make + sudo apt-get -y install curl + + # Apache + sudo apt-get -y install apache2 + sudo a2enmod rewrite + echo 'ServerName localhost' | sudo tee -a /etc/apache2/httpd.conf + sudo service apache2 restart + + # mysql + sudo echo "mysql-server-5.5 mysql-server/root_password password root" | debconf-set-selections + sudo echo "mysql-server-5.5 mysql-server/root_password_again password root" | debconf-set-selections + sudo apt-get -y install mysql-server-5.5 + + # php + sudo apt-get -y install php5 php5-dev php5-cli php-pear + sudo apt-get -y install php5-mysql php5-suhosin php-pear php5-curl php5-gd php5-imagick php5-mcrypt php5-memcache php5-xdebug php-apc + sudo apt-get -y install libapache2-mod-php5 + sudo service apache2 restart + + # Setup link + sudo ln -s /vagrant /var/www/airwolf + + # Delete index.html to have directory listing + sudo rm /var/www/index.html + + # Install Mongo + sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 + echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list + sudo apt-get update + sudo apt-get -y install mongodb-org + + # Mongo extension for php + no | sudo pecl install mongo + echo 'extension=mongo.so' | sudo tee -a /etc/php5/apache2/php.ini + echo 'extension=mongo.so' | sudo tee -a /etc/php5/cli/php.ini + sudo service apache2 restart + + # Install phpunit + sudo pear config-set auto_discover 1 + sudo pear install pear.phpunit.de/PHPUnit + + # Install php5-xsl extension + sudo apt-get -y install php5-xsl + echo 'extension=php_xsl.so' | sudo tee -a /etc/php5/apache2/php.ini + echo 'extension=php_xsl.so' | sudo tee -a /etc/php5/cli/php.ini + sudo service apache2 restart + + # Python xlrd + sudo apt-get -y install python-pip + sudo pip install xlrd + + # Admin Apps + + # Phpmyadmin + wget https://github.com/phpmyadmin/phpmyadmin/archive/RELEASE_4_1_3.tar.gz + tar -xzf RELEASE_4_1_3.tar.gz + rm RELEASE_4_1_3.tar.gz + sudo mv phpmyadmin-RELEASE_4_1_3/ /var/www/phpmyadmin + + # Genghis App + wget https://github.com/bobthecow/genghis/archive/v2.3.10.zip + unzip v2.3.10.zip + rm v2.3.10.zip + sudo mv genghis-2.3.10 /var/www/genghis + + # Some php configurations + + # Set display errors to On + # Author: olaf@flipside.org + PHP_FILE="/etc/php5/apache2/php.ini" + sudo sed -i "s/display_errors = Off/display_errors = On/" $PHP_FILE + + # Change AllowOverride None to AllowOverride All on line 11 for default site vhost. + # Multiline regex matched don't work with sed so I resorted to php. + # Better solution is welcome! + php -r '$f="/etc/apache2/sites-available/default";$d=file_get_contents($f);$d=preg_replace("/(.*?AllowOverride )None(.*?<\/Directory>)/s","$1All$2",$d);file_put_contents($f,$d);' + + sudo service apache2 restart + + + ####################################### + ## END PROVISIONING ## + ####################################### +fi + +if (( $CURRENT_VERSION < 2 )); then + CURRENT_VERSION=2 + echo "Installing version $CURRENT_VERSION" + ####################################### + ## START PROVISIONING ## + ####################################### + + sudo apt-get update + sudo apt-get install -y python-software-properties python g++ make + sudo add-apt-repository -y ppa:chris-lea/node.js + sudo apt-get update + sudo apt-get install -y nodejs + + sudo npm install -g casperjs + + sudo wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.7-linux-i686.tar.bz2 + sudo tar -xf phantomjs-1.9.7-linux-i686.tar.bz2 + sudo rm phantomjs-1.9.7-linux-i686.tar.bz2 + sudo mv phantomjs-1.9.7-linux-i686/ /opt/phantomjs + sudo ln -s /opt/phantomjs/bin/phantomjs /usr/bin/phantomjs + + ####################################### + ## END PROVISIONING ## + ####################################### +fi + +# Create file to make it run according to versions. +echo 'File to make Vagrant provisioning run according to versions. Do not delete.' > runonce.vagrant +echo 'Current version:' >> runonce.vagrant +echo $CURRENT_VERSION >> runonce.vagrant +exit; + +# NEW VERSION CODE +# Remove comments and change version number. +# Do not forget to change the version number at the top of the file. +<add(new DateInterval('PT' . $minutes_between_resets . 'M')); $interval = $last->diff(new DateTime()); - + // Is the reset date still in the future? if ($interval->invert === 1) { $seconds = $interval->s + $interval->i * 60 + $interval->h * 60 * 60; define('RESET_SECONDS_LEFT', $seconds); } else { - $demo_key = @file_get_contents($key_file); - // Set reset time and Redirect. - file_put_contents($control_file, serialize(new DateTime())); - header('Location: http://192.168.99.10/work/aw-datacollection/fixtures/all/' . $demo_key); - exit; + $demo_key = trim(@file_get_contents($key_file)); + + if (empty($demo_key)) { + die('Missing key.'); + } + + // Check if a reset request is already being made by CURL. + // Prevent additional requests from firing. + if (!isset($_GET['reset_key']) || $_GET['reset_key'] != $demo_key) { + $base_url = 'http://192.168.99.10/airwolf/'; + // Set reset time and perform request. + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $base_url . 'fixtures/all/?reset_key=' . $demo_key); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + $res = curl_exec($ch); + curl_close($ch); + + if ($res != 'Done') { + print $res; + exit; + } + + file_put_contents($control_file, serialize(new DateTime())); + header('Location: ' . $base_url); + exit; + } } -?> \ No newline at end of file +?> diff --git a/resources/valid_results/survey_3_results.xml b/resources/valid_results/survey_3_results.xml deleted file mode 100644 index 14af9cf..0000000 --- a/resources/valid_results/survey_3_results.xml +++ /dev/null @@ -1,135 +0,0 @@ - - 2014-03-21T15:42:25.000-00:00 - 2014-03-21T15:42:25.000-00:00 - 999999999999999 - 1 - - - - 1 - 1 - 2 - - 4 - 502305 - Banana - Tree - 2 - - - - - Banana - Tree - 1 - 1 - 1 - 1 - 34 - 17 - 3 - - 6 - - 123 - 1 4 - - 3 - - - - 2 - - - 2 - - - - - 3 - - - - - 3 - 3 - - - - - - - - 34 - 2 - 34 - 2 - - - - - 1 - - - - - - 2 - 4 - 2 - - - - - 1 - - 1 - - 2 - 2 - 2 - - - - 2 - - 3 - 3 - 3 - - - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/valid_survey/survey_1_xls.xls b/resources/valid_survey/survey_1_xls.xls deleted file mode 100644 index a4033a8..0000000 Binary files a/resources/valid_survey/survey_1_xls.xls and /dev/null differ diff --git a/resources/valid_survey/survey_1_xml.xml b/resources/valid_survey/survey_1_xml.xml deleted file mode 100644 index d9b82d3..0000000 --- a/resources/valid_survey/survey_1_xml.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - survey_1_xls - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - male - - - - female - - - - - - - - - - - You need to be outside for your GPS to work. - - - - - - yes - - - - no - - - - - - - - - - nyc - - - - chitown - - - - - - - - diff --git a/resources/valid_survey/survey_2_xls.xls b/resources/valid_survey/survey_2_xls.xls deleted file mode 100644 index 3a8721e..0000000 Binary files a/resources/valid_survey/survey_2_xls.xls and /dev/null differ diff --git a/resources/valid_survey/survey_2_xml.xml b/resources/valid_survey/survey_2_xml.xml deleted file mode 100644 index 12bd6f5..0000000 --- a/resources/valid_survey/survey_2_xml.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - survey_2_xls - - - - - - - - - - - - - - - - - - - - - - - - Enter numbers only. - - - - - - diff --git a/resources/valid_survey/survey_3_xls.xls b/resources/valid_survey/survey_3_xls.xls deleted file mode 100644 index 63657a7..0000000 Binary files a/resources/valid_survey/survey_3_xls.xls and /dev/null differ diff --git a/resources/valid_survey/survey_3_xml.xml b/resources/valid_survey/survey_3_xml.xml deleted file mode 100644 index 3b4fe5b..0000000 --- a/resources/valid_survey/survey_3_xml.xml +++ /dev/null @@ -1,1786 +0,0 @@ - - - - survey_3_xls - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please provide the phone number of the respondent (eg. 256779618452) - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - - - - - - - - - - - - - 1 - - - - 2 - - - - - - - 1 - - - - 2 - - - - - - - 1 - - - - 2 - - - - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - 7 - - - - 8 - - - - 9 - - - - 10 - - - - 11 - - - - 12 - - - - 13 - - - - - - - 502301 - - - - 502302 - - - - 502303 - - - - 502304 - - - - 502305 - - - - 502306 - - - - 502601 - - - - 502602 - - - - 502603 - - - - 502604 - - - - 502605 - - - - 502606 - - - - 502701 - - - - 502702 - - - - 502703 - - - - 11002501 - - - - 11002502 - - - - 11002503 - - - - 11002504 - - - - 11002505 - - - - - Of the respondent - - - - Of the respondent - - - - - - 1 - - - - 2 - - - - - - - - - - - - - - - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - 7 - - - - 8 - - - - 9 - - - - 10 - - - - 11 - - - - 12 - - - - 13 - - - - 14 - - - - - - - - Including those in boarding school - - - - - - 1 - - - - 2 - - - - - To the nearest year - - - - - - 0 - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - 7 - - - - 8 - - - - 9 - - - - 10 - - - - 11 - - - - 12 - - - - 13 - - - - 14 - - - - 15 - - - - 16 - - - - 17 - - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - 7 - - - - 8 - - - - 9 - - - - 10 - - - - - - - - In UGX - - - - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - - - - - - - 1 - - - - 2 - - - - 3 - - - - - In UGX - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - 7 - - - - 8 - - - - - - - 1 - - - - 2 - - - - - - - - - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - 7 - - - - 8 - - - - 9 - - - - 10 - - - - 11 - - - - - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - - - - 0 - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - 7 - - - - 8 - - - - 9 - - - - 10 - - - - 11 - - - - - - - - To the nearest hour - - - - - - - In UGX - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - - In liters - - - - In UGX - - - - - - 1 - - - - 2 - - - - 3 - - - - - In UGX - - - - - - - In UGX - - - - - - 1 - - - - 2 - - - - 3 - - - - - In UGX - - - - - - - In UGX (99 = didn't purchase it) - - - - - - - - - - - - 1 - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - 7 - - - - 8 - - - - 9 - - - - - In UGX - - - - - - - - - - - - 1 - - - - 2 - - - - - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - - - - - - - - - - - In UGX - - - - - - - - - - - - - - - - - 1 - - - - 2 - - - - - - - 1 - - - - 2 - - - - - - - 1 - - - - 2 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - In UGX - - - - Or another device if you already own one - - - 1 - - - - 2 - - - - - - - 1 - - - - 2 - - - - 3 - - - - 4 - - - - 5 - - - - 6 - - - - 7 - - - - - - - - In UGX - - - - Please read the options - - - 1 - - - - 2 - - - - - - - - - - - - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - 1 - - - - 2 - - - - 3 - - - - - - - - diff --git a/src/styles/_demo_styles.scss b/src/styles/_demo_styles.scss index 73e0271..ccfd29f 100644 --- a/src/styles/_demo_styles.scss +++ b/src/styles/_demo_styles.scss @@ -82,4 +82,26 @@ margin-bottom: 0; } } +} + +.ribbon { + position: fixed; + z-index: 101; + top: 2rem; + right: -3rem; + margin: 0; + overflow: hidden; + background-color: palette(blue); + box-shadow: 0 4px 0 0 rgba(69, 77, 83, 0.1); + transform: rotate(45deg); + white-space: nowrap; + + a { + color: #fff; + font-size: rem-calc(14); + display: block; + padding: 0.25rem 1rem; + text-align: center; + width: 12rem; + } } \ No newline at end of file