We've recently launched http://apcards.ru (in Russian), Ubercart powered shop for greeting cards manufacturer.
That was an interesting project, and I would like to share the story of the project with the community.

Website screenshot: http://sidashin.ru/my_files/apcards/apcards.jpg

Goals

The main website goal was to create a beautiful modern website where it's convenient for customers to quickly buy hundreds of different products (greeting cards) in 2-3 mouse clicks. The typical visitor of the website is greeting cards reseller.

Views vs Catalog

At the very beginning we've moved from native Ubercart catalog to views powered catalog to take advantage of exposed filters for quick product search form. That was a no-brainer, as views provides much more flexible theming and a lot of crucial features.

The problem of multibuy interface was solved with uc_multibuy + some modifications to views template files to make it work with views:
http://pix.am/nn3G.png

jQuery magic

The views powered exposed search was improved by adding some jquery and ajax magic - we've added 'live' search results count calculation at the moment when customer clicks some search checkbox or changes selectbox state:
http://pix.am/HJQO.png

Customers can use "+" and "-" buttons to change the amount of products that will be added to cart.

In cart, there is another cool addition - live discount calculation (jquery powered). When user enters his discount, the price for each item in cart is updated without page refresh.
http://pix.am/FPwv.png

More features

Customers also have "quick order history" feature in cart, to see if they already ordered that product in previous order:
http://pix.am/RoFh.png (on this specific item screenshot, the text on yellow background says "No such item in previous order"
That was implemented by tapir hook in our site helper module - nice and elegant solution. Here is the code:

function site_helper_tapir_table_alter(&$table, $table_id) {

  if ($table_id != 'uc_cart_view_table') return;
  global $user;     
  
  if ($user->uid) {
    $order = uc_order_load(db_result(db_query("SELECT order_id FROM {uc_orders} WHERE uid=%d ORDER BY created DESC LIMIT 1", $user->uid)));
    if (!empty($order)) {
      $prev_items = array();
      foreach ($order->products as $p) {
        $prev_items[$p->model] = $p->qty;
      }
      
      foreach (element_children($table) as $key) {
        if (empty($table[$key]['nid']['#value'])) continue;
        
        $n = node_load($table[$key]['nid']['#value']);
        if ($prev_items[$n->model]) {
          $table[$key]['desc']['#value'] .= ' <span class="prev-order-info prev-order-exist">В предыдущем заказе: ' . $prev_items[$n->model] . ' шт</span>';
        } else {
          $table[$key]['desc']['#value'] .= ' <span class="prev-order-info prev-order-zero">В предыдущем заказе: нет</span>';
        }

      }
    }
  }

}

Features on top of CCK fields is another interesting point of the project:
we've implemented "downloads section" and "special offers" by adding additional programmatic layer over CCK.
"Special offers" are just CCK flags in "Product" node, but site operator can easily update these offers titles and deactivate some of them, using settings form built by us for this specific task. (It was important to make the admin section of the website easy to use for non-technical staff)

Special offers form screenshot: http://pix.am/E01E.png

Under the hood, that is a code that updates CCK fields settings when operator submits our settings form.

The code looks like this:

function site_helper_special_offers_submit($form, $form_state) {
  $offer1_title = $form_state['values']['offer1_title'];
  $field = unserialize(db_result(db_query("SELECT global_settings FROM {content_node_field} WHERE field_name='field_p_special_1'")));
  $field['allowed_values'] = "0|Нет\n1|$offer1_title";
  db_query("UPDATE {content_node_field} SET global_settings='%s' WHERE field_name='field_p_special_1'", serialize($field));
  db_query("UPDATE {content_node_field_instance} SET label='%s' WHERE field_name='field_p_special_1' AND type_name='product' LIMIT 1", $offer1_title);
  variable_set('offer1_status', $form_state['values']['offer1_status']);
  
  $offer2_title = $form_state['values']['offer2_title'];
  $field = unserialize(db_result(db_query("SELECT global_settings FROM {content_node_field} WHERE field_name='field_p_special_2'")));
  $field['allowed_values'] = "0|Нет\n1|$offer2_title";
  db_query("UPDATE {content_node_field} SET global_settings='%s' WHERE field_name='field_p_special_2'", serialize($field));
  db_query("UPDATE {content_node_field_instance} SET label='%s' WHERE field_name='field_p_special_2' AND type_name='product' LIMIT 1", $offer2_title);
  variable_set('offer2_status', $form_state['values']['offer2_status']);

  drupal_set_message(t('Settings were saved!'));
  content_clear_type_cache();
  
}

The similar approach was implemented for downloads section - it was a separate “file” node type, with “file” (CCK filefield) and “file category” selectbox (CCK text) - and by using awesome filefield_paths module we could make it so the new .xls file upload to the category deleted the previous file node of the same category, and used old and human-readable url, like http://apcards.ru/files/[file-type].xls

But guys from apcards.ru asked us to provide interface to add more file types when they need. We tried to explain that they need to go to cck field settings form and modify “Allowed values” array - but that was too hard for non-technical operators. So we came up with another settings form, that modifies the allowed values of CCK file-type field on submit, the settings form looks like this: http://pix.am/GLHz.png and the submit code looks like this:

function site_helper_file_types_submit($form, $form_values) {
  foreach($form_values['values'] as $k=>$v) {
    if (is_numeric($k) && !empty($v['key']) && !empty($v['value'])) {
      $items[] = $v['key'].'|'.$v['value'];
    }
  }
  if (count($items)) {
    
    $field = unserialize(db_result(db_query("SELECT global_settings FROM {content_node_field} WHERE field_name='field_file_type'")));
    $field['allowed_values'] = implode("\n", $items);
    db_query("UPDATE {content_node_field} SET global_settings='%s' WHERE field_name='field_file_type'", serialize($field));
    content_clear_type_cache();
    drupal_set_message(t('Settings were saved!'));
  }
}

The files section screenshot: http://pix.am/dfJe.png

There are many more small custom features on the website, like “new” indicator on products based on latest customer order date, products import from 1C software, and others that I forgot about :)

Design and theme

The design was created by our designer Oleg Sidashin, that was a multistep process:

  1. Wireframes creation
  2. Design creation
  3. Details

Oleg managed to create a very clean design and converted it to Drupal theme (we've used our Pixeljets Core base theme that increased the speed of theming process a lot).
We know that theme looks really good only when designer pays big attention to details not just during .psd mockup creation, but after converting the design to Drupal theme. There are always some places that need to be polished when the Drupal theme is already created, and Oleg did a great job here, adding small pixel-perfect gradients, icons and shadows.

Happy end

Guys from Apcards.ru loved Drupal and our design work, and now the website is live and already accepted more than 100 real orders for several weeks, without any customer complaints.

The website address: http://apcards.ru

Team

Developer: Anton Sidashin
Designer: Oleg Sidashin

The website was created by http://pixeljets.com

Your feedback is welcome!