{"id":13818944,"url":"https://github.com/loralee90/Quickstarter","last_synced_at":"2025-05-16T04:31:58.464Z","repository":{"id":216966566,"uuid":"94771000","full_name":"loralee90/Quickstarter","owner":"loralee90","description":"A single-page app inspired by Kickstarter that lets you start and fund projects. Built with ES6, React, Redux and Ruby on Rails","archived":false,"fork":false,"pushed_at":"2019-05-08T05:20:12.000Z","size":18621,"stargazers_count":30,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-19T18:45:04.427Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/loralee90.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-06-19T11:46:29.000Z","updated_at":"2024-04-04T11:36:54.000Z","dependencies_parsed_at":"2024-02-02T02:00:22.300Z","dependency_job_id":null,"html_url":"https://github.com/loralee90/Quickstarter","commit_stats":null,"previous_names":["loralee90/quickstarter"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loralee90%2FQuickstarter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loralee90%2FQuickstarter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loralee90%2FQuickstarter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loralee90%2FQuickstarter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/loralee90","download_url":"https://codeload.github.com/loralee90/Quickstarter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254469040,"owners_count":22076418,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T08:00:36.155Z","updated_at":"2025-05-16T04:31:54.973Z","avatar_url":"https://github.com/loralee90.png","language":"Ruby","funding_links":[],"categories":["Happy Exploring 🤘"],"sub_categories":[],"readme":"# Quickstarter\n\n[Quickstarter live](https://quickstarter-ll.herokuapp.com/#/)\n\nInspired by Kickstarter, Quickstarter is a single-page web application where users can start and fund projects. It was built with Ruby on Rails, ES6, React and Redux.\n\n![Quickstarter carousel](https://github.com/loralee90/Quickstarter/blob/master/docs/images/carousel.png)\n![Quickstarter project index](https://github.com/loralee90/Quickstarter/blob/master/docs/images/project_index.png)\n\n## Features and Implementation\n\n### Project Creation\n\nUsers can create projects and add rewards on the project form page.\n\n![gif](http://g.recordit.co/mp0UVAIi7y.gif)\n\nThe project form consists of two elements - the Basics form which contains all the project information, and the Rewards form which contains reward information. Users are able to navigate between the two forms without losing previously typed information. The rewards form also contains an \"Add a new reward\" button which allows users to dynamically add as many rewards as desired.\n\n### ProjectForm\n\nThe challenge was having two different form components (Basics and Rewards), while being able to access information from both in order to create one project. To achieve this, I created a `ProjectForm` component that houses the `BasicsForm` and `RewardsForm` components. `ProjectForm` then contains a local state that stores information from both forms.\n\n```javascript\nthis.state = {\n  formType: \"basics\",\n  title: \"\",\n  description: \"\",\n  end_date: \"\",\n  funding_goal: 0,\n  details: \"\",\n  category_id: 0,\n  rewardsNums: [1],\n  rewards: {\n    1: {title: \"\", description: \"\", cost: 0, delivery_date: \"\"}\n  },\n  imageFile: null,\n  imageUrl: null\n};\n```\n\nThe state also keeps track of the formType which updates when a user clicks on either the Basics or Rewards navigation buttons. This information is passed down to the child components, which only render when the formType matches their own (\"basics\" for the `BasicsForm` and \"rewards\" for the `RewardsForm`).\n\nIn the backend, I implemented the `accepts_nested_attributes_for` ActiveRecord method in the `Project` model in order to create projects and rewards simultaneously while nesting rewards with their associated projects. My `ProjectsController` accounts for this as well.\n\n```Ruby\ndef project_params\n  params\n    .require(:project)\n    .permit(:title, :image, :url, :description, :end_date, :funding_goal, :details, :category_id, rewards_attributes: [:title, :description, :cost, :delivery_date])\nend\n```\n\nWhen a user submits a project, the data passed into the `createProject` action also contains the rewards.\n\n```javascript\nhandleSubmit(e) {\n  e.preventDefault();\n  const formData = new FormData();\n  formData.append(\"project[title]\", this.state.title);\n  formData.append(\"project[description]\", this.state.description);\n  formData.append(\"project[end_date]\", this.state.end_date);\n  formData.append(\"project[funding_goal]\", this.state.funding_goal);\n  formData.append(\"project[details]\", this.state.details);\n  formData.append(\"project[category_id]\", this.state.category_id);\n  formData.append(\"project[rewards_attributes]\", JSON.stringify(values(this.state.rewards)));\n\n  if (this.state.imageFile) {\n    formData.append(\"project[image]\", this.state.imageFile);\n  }\n\n  if (this.props.project) {\n    this.props.updateProject(this.props.project.id, formData)\n      .then(data =\u003e this.props.history.push(`/projects/${data.project.id}`));\n  } else {\n    this.props.createProject(formData)\n    .then(data =\u003e this.props.history.push(`/projects/${data.project.id}`));\n  }\n}\n```\n\nIn order to keep my code DRY, I use the `ProjectForm` component for my project edit functionality as well, which is why the `handleSubmit` function checks for a project in the props. If there is a project, the `updateProject` action is fired. Otherwise, the `createProject` action is fired.\n\n### RewardsForm\n\nHere, I needed the capability to add rewards and also access new rewards in my `ProjectForm`. I achieved this by first keeping track of the `rewardsNums` in my `ProjectForm` state. When a user clicks the \"Add a new reward\" button, the `updateReward` function is invoked. `updateReward` is a function passed down from `ProjectForm` to `RewardsForm` as a prop, and is actually bound to `ProjectForm`, setting its state.\n\n```javascript\nupdateReward(rewardNum, field) {\n  if (this.state.rewardsNums.includes(rewardNum)) {\n    return e =\u003e {\n      this.setState({rewards: merge({}, this.state.rewards, {[rewardNum]: {[field]: e.currentTarget.value}})});\n    };\n  } else {\n    let rewardsNums = this.state.rewardsNums.slice();\n    rewardsNums.push(rewardNum);\n    const newRewards = merge({}, this.state.rewards, {[rewardNum]: {title: \"\", description: \"\", cost: 0, delivery_date: \"\"}});\n    this.setState({rewardsNums, rewards: newRewards});\n  }\n}\n```\n\nThe `ProjectForm` passes the new state to `RewardsForm`, where it renders new `RewardsFormItem`s based on `rewardsNums`.\n\n```javascript\n{this.props.state.rewardsNums.map(\n  num =\u003e \u003cRewardsFormItem key={num} rewardNum={num} updateReward={this.props.updateReward} state={this.props.state} /\u003e\n)}\n```\n### Project Search\n\nUsers can make a live search for projects whose titles, descriptions, details, or project creators match the search.\n\n![gif](http://g.recordit.co/JpgcCKxfVx.gif)\n\nIn order to implement a live search, I added a listener for changes to the search input. Each change fires an AJAX request to fetch search results. The `SearchForm` component receives the results as props and renders the new results each time.\n\nIn the backend I created a search route nested under projects.\n\n```Ruby\nresources :projects, except: [:new, :edit] do\n  get \"search\", on: :collection\nend\n```\nThe `ProjectsController` has a search method that makes an ActiveRecord query for case-insensitve matches.\n\n```ruby\ndef search\n  search = params[:search].downcase\n\n  if params[:search].present?\n    @projects = Project\n      .joins(:creator)\n      .where(\n        \"lower(title) ~ :search OR lower(description) ~ :search OR lower(details) ~ :search OR lower(users.name) ~ :search\",\n         {search: search})\n    render :search\n  end\nend\n```\n\n### Pledging\n\nUsers can make a pledge to either projects or rewards.\n\n![gif](http://g.recordit.co/uiZRkp9viE.gif)\n\nThis is accomplished with polymorphic associations between the `Pledge`, `Project`, and `Reward` models.\n\n`Pledge` model:\n\n```Ruby\nclass Pledge \u003c ApplicationRecord\n  validates :amount, :pledgeable_id, :pledgeable_type, :backer_id, presence: true\n  validates_numericality_of :amount, greater_than: 0\n\n  belongs_to :pledgeable, polymorphic: true\n  belongs_to :backer,\n  class_name: :User,\n  primary_key: :id,\n  foreign_key: :backer_id\nend\n```\n\nBoth `Project` and `Reward` models have the following association:\n\n```ruby\nhas_many :pledges, as: :pledgeable\n```\n\nI also wanted to create an interactive experience for users making a pledge. Clicking a reward or project pledge box opens up a form and highlights the border to indicate activity. To achieve this, I nested a `RewardPledgeForm` component inside my `RewardListItem` component. `RewardListItem`'s local state indicates whether or not a form should be rendered.\n\n## Future Directions for the Project\n\nI plan on continuing to improve upon the already implemented features and also adding the features below.\n\n### Likes\n\nUsers will be able to \"like\" projects so they can quickly save and reference the projects they've liked.\n\n### User profile\n\nIn order for users to keep track of their activity, I plan on building out the user profile. Users will be able to see the projects they've started as well as funded. They will also be able to upload an avatar photo and change account details.\n\n### Credit card payments\n\nI plan on adding credit card payment and authentication functionality to fully equip the app for consumer use.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floralee90%2FQuickstarter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Floralee90%2FQuickstarter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floralee90%2FQuickstarter/lists"}