{"id":13818947,"url":"https://github.com/griffinsharp/kicker","last_synced_at":"2025-05-16T04:31:59.911Z","repository":{"id":47944079,"uuid":"218224180","full_name":"griffinsharp/kicker","owner":"griffinsharp","description":"A full-stack website clone of Kickstarter built using Ruby on Rails, PostgreSQL, React, and Redux.","archived":false,"fork":false,"pushed_at":"2021-08-11T15:54:28.000Z","size":18616,"stargazers_count":6,"open_issues_count":15,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-11-19T18:45:08.637Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://kicker-app.herokuapp.com/#/","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/griffinsharp.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}},"created_at":"2019-10-29T07:02:54.000Z","updated_at":"2024-03-24T22:14:15.000Z","dependencies_parsed_at":"2022-08-12T14:50:40.717Z","dependency_job_id":null,"html_url":"https://github.com/griffinsharp/kicker","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/griffinsharp%2Fkicker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/griffinsharp%2Fkicker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/griffinsharp%2Fkicker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/griffinsharp%2Fkicker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/griffinsharp","download_url":"https://codeload.github.com/griffinsharp/kicker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254469069,"owners_count":22076423,"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.181Z","updated_at":"2025-05-16T04:31:56.974Z","avatar_url":"https://github.com/griffinsharp.png","language":"Ruby","funding_links":[],"categories":["Happy Exploring 🤘"],"sub_categories":[],"readme":"\u003ca href=\"https://kicker-app.herokuapp.com/#/\" target=\"_blank\" \u003e\n\u003cp align=\"center\"\u003e \n\u003cimg src=\"https://github.com/griffinsharp/Kicker/blob/master/app/assets/images/logo.png\"\u003e\n\u003c/p\u003e\n\u003c/a\u003e\n\n## Introduction 👟\n\n[Kicker](https://kicker-app.herokuapp.com/#/), a [Kickstarter](https://www.kickstarter.com/) clone, is a crowdfunding application that allows users to collectively help get innovative sneaker related projects, products, and services to the market. If the project receives full funding by the end of its campaign, project backers receive a tiered reward contingent on the amount they pledged. Sneakerheads, rejoice!\n\nKicker's design documents, such as the [database schema](https://github.com/griffinsharp/Kicker/wiki/Database-Schema), [sample state](https://github.com/griffinsharp/Kicker/wiki/Sample-State), and [frontend](https://github.com/griffinsharp/Kicker/wiki/Frontend-Routes)/[backend](https://github.com/griffinsharp/Kicker/wiki/Backend-Routes) routes, can be found on the [Wiki](https://github.com/griffinsharp/Kicker/wiki).\n\n## Features ⚡️\nHere are a few screenshots/gifs of Kicker. **To experience the full functionality of the site (back projects, create projects, etc.), please create an account or login as a demo user!**\n\n**Site Landing/Home Page**\n\nThis is what users are greeted with upon navigating to the website. A navbar, one featured project, reccomended projects, call to action banners, and some curated projects.\n\n\u003cp align=\"center\"\u003e \n\u003cimg src=\"https://github.com/griffinsharp/Kicker/blob/master/app/assets/images/homepage.gif\"\u003e\n\u003c/p\u003e\n\n**Project Page with Reward Tiers**\n\nContributing to a project should update the total amount pledged immediately. If the project is featured on the home page or its category's page, it should update there too. \n\n\u003cp align=\"center\" \u003e \n\u003cimg src=\"https://github.com/griffinsharp/Kicker/blob/master/app/assets/images/projdonate.gif\"\u003e\n\u003c/p\u003e\n\n**Project Creation**\n\nUsers can create their own projects, able to customize their campaign by title, subtitle, description, company bio, category, project location, goal amount, and campaign duration. If you're in a rush and just want to see your project live, values for these fields will automaticaly be filled in for you. \n\n\u003cp align=\"center\" \u003e \n\u003cimg src=\"https://github.com/griffinsharp/Kicker/blob/master/app/assets/images/projcreate.gif\"\u003e\n\u003c/p\u003e\n\n**Search Results**\n\nUsers can filter projects by category and location, and sort by company favorites, newest, nearly funded, end date, most backed, and random.\n\n\u003cp align=\"center\"\u003e \n\u003cimg src=\"https://github.com/griffinsharp/Kicker/blob/master/app/assets/images/search.gif\"\u003e\n\u003c/p\u003e\n\n**Categories**\n\nEssentially, each category has its own 'home' page with its own featured, reccomended, and handpicked projects to dipslay. Here I was able to reuse the home page components with varying content, keeping my code DRY and reusable, while dynamically rendering different header descriptions based on the url `/category/:categoryId` thanks to `React Router`. \n\n\u003cp align=\"center\"\u003e \n\u003cimg src=\"https://github.com/griffinsharp/Kicker/blob/master/app/assets/images/categories.gif\"\u003e\n\u003c/p\u003e\n\n**Demo User**\n\nMany user actions on Kicker require an account (creating or backing a project, for example), but I didn't want to make this a barrier to accessing all the site's features. To get around this, the user signup/login pages have a 'demo user' option if one wants to get the full experience without taking the time to sign up. Attempting one of these protected actions without being logged in will direct the user to the sign in page. A red error bar will appear, explaining why the redirection occured, and after signing in, the user will then be re-directed to whatever page they were enjoying previously. \n\n**Explore Modal**\n\nClicking \"explore\" on the navigation bar allows for quick access to many parts of the website via a pop-up modal. \n\n\u003cp align=\"center\" \u003e \n\u003cimg src=\"https://github.com/griffinsharp/Kicker/blob/master/app/assets/images/newmodal.gif\"\u003e\n\u003c/p\u003e\n\n## Code Snippets ⚛️\n\n**Session Component**\n\nThis component renders both the login and sign up forms. This was done via using two different `Redux` containers for `/signup` or `/login`, and providing the proper state (form type, links, errors, etc.) and actions (login or logout) accordingly. `React` components should only really be concerned with the rendering of information, but one is able to change what this information via props. \n\n```javascript\nclass SessionForm extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n      repeatPassword: \"hidden\",\n      repeatEmail: \"hidden\"\n    },\n    this.handleSubmit = this.handleSubmit.bind(this);\n    this.handleErrors = this.handleErrors.bind(this);\n    this.handleClick = this.handleClick.bind(this);\n    this.handleEmail = this.handleEmail.bind(this);\n    this.demoUserLogin = this.demoUserLogin.bind(this);\n  }\n\n  update(field) {\n    return e =\u003e this.setState({ [field]: e.currentTarget.value });\n  }\n\n  handleSubmit(e) {\n    e.preventDefault();\n    const user = Object.assign({}, this.state);\n    this.props.processForm(user).then(() =\u003e this.handleErrors());\n  }\n\n  handleClick() {\n    if (this.props.location.pathname === \"/signup\") {\n      this.setState({ repeatPassword: \"session-type-input s-two\" });\n    }\n  }\n  \n  // ...rest of component\n}\n```\n\n**Projects Reducer**\n\nThis `Redux` reducer is reponsible for listening for various actions, such as when a single project's information is requested or a project is backed, and updates the [project slice of state](https://github.com/griffinsharp/Kicker/wiki/Sample-State) accordingly. If an action is executed, and the project reducer doesn't have a case to execute a reponse (such as logging out the current user), it will return the previous state.\n\n```javascript\nconst projectsReducer = (oldState = {}, action) =\u003e {\n  Object.freeze(oldState);\n  let newState = Object.assign({}, oldState);\n\n  switch (action.type) {\n    case RECEIVE_PROJECTS:\n      return action.projects;\n    case RECEIVE_PROJECT:\n      newState[action.payload.project.id] = action.payload.project;\n      return newState;\n    case RECEIVE_BACKING:\n      newState[action.payload.project.id] = action.payload.project;\n      return newState;\n    default:\n      return oldState;\n  }\n};\n```\n\n**Backings Controller**\n\nHere is a fairly straightforward `Rails` controller. When an ajax `POST` request is made to `/api/backings` to create a new backing for a project, if given the right parameters with a logged in user, the backing will be saved to the database with the proper reward, project, and user associations. A `api/backings/show` view is rendered and presented via jBuilder, specifying what information is needed on the frontend to account for this change. \n\n```ruby \nclass Api::BackingsController \u003c ApplicationController\n\n    def index\n        @backings = Backing.all\n        render 'api/rewards/index'\n    end\n\n    def create\n        @backing = Backing.new(backing_params)\n        @backing.user_id = current_user.id\n        \n        if @backing.save\n            render 'api/backings/show'\n        else\n            render json: [\"You need to be signed in to pledge to a project.\"], status: 401\n        end\n    end\n\n    private\n\n    def backing_params\n        params.require(:backing).permit(:user_id, :reward_id, :project_id, :backing_amount)\n    end\n\nend\n```\n\n**User Model + Authentication**\n\nKicker's user authentication utilizes the `BCrypt` gem to safely hash and salt password, avoiding the storage of password in a plain-text format. There are `Rails` backend model and migration level validations, such as password length and email uniqueness, to require users to sign up with valid credentials, and the failure to do so results in the rendering of an associated error.\n\n```ruby\nclass User \u003c ApplicationRecord\n\n# confirm our 'null: false' database constraint on the model level\nvalidates :email, :name, :session_token, :password_digest, presence: true\n# confirm our 'unique: true' database constraint on the model level\nvalidates :email, uniqueness: true\nvalidates :password, length: {minimum: 6}, allow_nil: true\n\nattr_reader :password \n\nbefore_validation :ensure_session_token\n\nhas_many :projects,\nforeign_key: :user_id,\nclass_name: :Project\n\nhas_many :backings,\nforeign_key: :user_id,\nclass_name: :Backing\n\nhas_many :rewards,\nthrough: :backings,\nsource: :reward\n\ndef self.find_by_credentials(email, password)\n    user = User.find_by(email: email)\n    return nil unless user \u0026\u0026 user.is_password?(password)\n    user\nend\n\n# Make our password digest equal to a bcrypt object we\n# generated using the user's password.\ndef password=(password)\n    @password = password\n    self.password_digest = BCrypt::Password.create(password)\nend\n\ndef is_password?(password)\n    BCrypt::Password.new(self.password_digest).is_password?(password)\nend\n\n# If we have a session token already, use that value.\n# If we do not, create a new session token, thus ensuring one exists.\ndef ensure_session_token\n    self.session_token ||= SecureRandom.urlsafe_base64\nend\n\ndef reset_session_token!\n    self.session_token = SecureRandom.urlsafe_base64\n    self.save!\n    self.session_token\nend\n\nend\n```\n\t\t\n## Technologies Used 📟 \n\n- `Ruby on Rails` \n- `React`\n- `Redux`\n- `PostgreSQL` \n- `jBuilder` \n- `Webpack` + `Babel` \n\n## Future Plans 🤷‍♂️\n\n- I have plans to make Kicker mobile responsive and fully avaiable to users with web accessibility issues.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgriffinsharp%2Fkicker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgriffinsharp%2Fkicker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgriffinsharp%2Fkicker/lists"}