Limit user tracking in a Laravel Spark application

Laravel Spark comes with a really handy feature: user impersonation. The “kiosk” allows you to search for users of your application, and click a button to sign in as that user. This is great for things like white-glove onboarding, recording screencasts for a user to show them a feature, etc.

It’s quite common to use something like Intercom, FullStory, etc. to track users in your application. You might want to keep tabs on how often they log in, what actions they take, and so on. But, if you use that impersonation feature, you’ll trigger that tracking for a user’s account when they aren’t actually using the app! And, you probably don’t care about using something like FullStory to track your own usage of the app.

I wanted a way to avoid this problem, and after some digging, I discovered that Spark sets a spark:impersonator session key when using the impersonation feature. As a result, we can check for that in our app’s app.blade.php layout file, and conditionally set a javascript variable:

We can then check for that variable in our javascript, and skip user tracking if it’s present.

Making axios work with external resources in Laravel Spark apps

In MemberScore, we make use of a WordPress plugin to connect a user’s site to MemberScore. The easiest way to provide that plugin is to give users a zip file that they can upload through their WordPress admin area. I could ship a zip file with my Laravel application, but since I’m already hosting that plugin’s code on Github, it seems silly to also add it to the Laravel application, and to update the file each time I make a change to the plugin code.

Github does give you a /latest release URL that redirects to the most recent release of a repository, but that would still require users to then click on the correct download link – plus it’s an extra step.

Instead, I opted to make use of the Github API, which returns that latest release as a json object: https://api.github.com/repos/tnorthcutt/member-score-wp-plugin/releases/latest. I’m then able to grab the first element of the assets array in that object, and use the browser_download_url. In my Vue component:

You’ll notice there that I’m fetching the Github URL with axios. By default, Laravel Spark makes use of axios, a handy dandy “promise based HTTP client for the browser and node.js.” Axios is very convenient to use. However, Spark also sets some default headers on requests sent with axios:

As a result, you may run into some issues using axios for external requests (outside of your application). Here’s a Laracasts thread explaining just that problem. This was the case for me; performing that request resulted in this nasty error message:

Access to XMLHttpRequest at 'https://api.github.com/repos/tnorthcutt/member-score-wp-plugin/releases/latest' from origin 'https://app.memberscore.io' has been blocked by CORS policy: Request header field X-CSRF-TOKEN is not allowed by Access-Control-Allow-Headers in preflight response.

The solution turns out to be deleting the X-CSRF-TOKEN header just before making the request, then adding it back afterwards (so we don’t break things for other parts of the application):

While this seems like a bit of a messy workaround, it does indeed work. There is currently a pull request on the axios repository to allow setting a header to null on an individual request and not sending that header as a result; that would be far preferable. As of this writing, axios still sends an empty header if you set it to null, which breaks Access-Control-Allow-Headers.

Converting Laravel Spark navigation to Tailwind CSS

I recently converted the navigation of my Laravel Spark based app, MemberScore, to use Tailwind CSS. Spark uses Bootstrap out of the box, which I wanted to move away from for two reasons:

  1. I find it a bit clunky and restricting, personally
  2. I’d built a sales site for MemberScore (at memberscore.io), and I wanted the actual app (at app.memberscore.io) to match the sales site appearance.

I haven’t converted the entire app to use Tailwind (yet?), but the nav is now converted and fully functional.

Here’s how I did it

Essentially, we’re going to recreate all the nav-related Spark views, and swap out the various css classes to take advantage of Tailwind, removing Bootstrap-related ones and instead using Tailwind classes to get the nav looking how we want.

The first step is to install Tailwind. If you’re not sure whether you’ll want to make this change permanently, I highly recommend including Tailwind via CDN as referenced in the docs, rather than going through the relative hassle of including it via npm.

If you’re ready to commit, you’ll need to include tailwindcss in your package.json file. I also needed to bump the version of laravel-mix I was loading, from ^0.12.0 to ^2.0. I also included laravel-mix-tailwind.

In my webpack.mix.js file, I was then able to follow the instructions provided by laravel-mix-tailwind, and get Tailwind compiling ok after adding the tailwind directives to the end of my app.less file:

You’ll notice the bootstrap-overrides.less file there; I had to make a couple of additions to override some default Bootstrap files that didn’t play well with Tailwind:

That may not be the Right Way™ to do this, but it’s working ok for me.

Next, I had to figure out how to get two things working:

  1. The toggling of the main nav menu on small screens
  2. The account switcher/settings/kiosk etc. dropdown in the nav

I ended up creating a single file Vue template called MainNav.vue and using it as an inline template; this let me still use blade helpers within it. Here’s the template:

And where it’s used in resources/views/nav/user.blade.php:

Toggling visibility

Notice the navOpen and dropdownOpen props, and associated toggleNav() and toggleDropdown() methods; those handle showing/hiding the two items mentioned previously. You can see there’s a button that calls toggleNav(), and the main div containing the nav items applies a class, either block or hidden, depending on the current value of navOpen (which is what toggleNav() alters).

The dropdown toggling is done exactly the same; you can see dropdown-toggle.blade.php is included in the main nav file. For some reason, I ended up with dropdown.blade.php being @include()-ed in dropdown-toggle.blade.php instead other way around, but the names don’t really matter.

Wrap up

That’s the general gist of how I did this. I’m sure this isn’t all done as perfectly as it could be, but it’s functional, looks how I want it, and I understand it – and those three things go a long way in production.

If you have any questions, or suggestions, shoot me an email at travis@travisnorthcutt.com. I’d love to hear from you.

Bad Design™

I tweeted this recently, and it deserves a little more explanation (and permanence):

The key quote:

We’ve now learned Google will also introduce “Confidential Mode,” which lets Gmail users stop recipients from forwarding certain emails, or restricts the ability to copy, download, or print them.

Here’s the thing: if you send someone an email – even if it “expires” after a set amount of time, or gmail somehow prevents them from copying/downloading/printing text and/or images from it, they can still share it. All they have to do is take a screenshot – or a literal picture with, say, their phone – and share that.

Picture of this post

Look, I took a picture with my phone. This isn’t hard.

Sure, this has some utility. For instance, you could share a password with someone with an “expiring” email, and be sure (not that I’d trust Google to actually delete anything…)  it won’t be sitting in their email inbox forever, just waiting for a future hacker to come along and find it.

But promoting this as a way to prevent sharing of information is misleading, user-hostile, and bad design. Period, full stop.

Using Drip trigger links for fun and profit

In a recent email to my list (sign up if you run a membership site or if you like awesome gifs), I recommended that any membership site without a discussion forum should seriously consider setting one up (building community is one of the best ways to keep members sticking around for longer). My forum software of choice is Discourse, so I put together a very quick post on how to set up Discourse. Nothing fancy, just some basic instructions, and more importantly in the context of the email, it was something to link to.

Then, in my Drip broadcast, I set up the link to that post as a trigger link:

trigger link

I then set up a workflow to run when that link is clicked:

discourse workflow

Now, I’ve established that there’s interest in setting up Discourse, and I have an extremely targeted list of people to market to, offering to do that for them. I’ll likely promote that service more widely as well, but I can push it more heavily to that list. Repeat with multiple other products & services, and using trigger links in your emails becomes a pretty powerful tactic.

Watsi for my birthday

Today’s my birthday! I made it another year, and I am extremely blessed, most especially by my wonderful wife Amanda and our son Kyle.

I have one request for all of you today: please consider donating to http://watsi.org/, my favorite charity. Watsi funds healthcare for those in need around the world, and does so in a radically transparent way (all of their financials are publicly visible). 100% of your donation goes directly to fund medical care; operating expenses are covered by foundations, philanthropists, and donors who leave an optional tip.

If you don’t feel called to donate or can’t afford to right now, that’s totally fine! But please share this; perhaps someone else will. And have a great day!

And if you do donate, please comment here or let me know on twitter!

Mastermind groups are awesome

December 2015 update: I wrote a book on freelancing/consulting with my mastermind group. Learn more about that here.

Freelancing Consulting can get lonely

As a freelancer (don’t use that word!), more than likely you work alone a lot of the time. Whether that’s in a home office, from a coffee shop, or a shared office space, you’re the only one working on your business. Now, working alone can be great, of course; it means you’re the sole decision-maker, you have complete autonomy over how you run your business, you get to decide how to deal with difficult client situations, etc.

The problem is, sometimes outside input can be immensely valuable! Other people often have valuable perspectives to offer, based on their experience and their lack of emotional investment in a situation.

Having a business partner (which I do) helps with that, but even so, it can still be great to get input from another trusted source. That’s why I think being part of a mastermind group is one of the most valuable changes you can make in your business.

A little over four months ago, Michael and I joined a mastermind group with Nick, Jane, Philip, Zack, Kai, Kurt, Jeremy, and Jonathan. Joining this group has been the single most valuable change we’ve made to our business in… ever. That may sound extreme or flippant, but I believe it’s true. We continually push each other to be better at what we do, help each other communicate better with clients, deliver (and charge for!) more value, and encourage each other.

Convinced, but don’t know what to do next? Read on.

Talk to people

The only reason I’m in this mastermind group is because Nick somehow knew of me, and knew we were working on a productized consulting service (inspired by his, as a matter of fact). I think perhaps Philip also knew of me, and they colluded on starting the group, but I can’t remember the details. However, I do know that it only happened because I’d interacted with them on Twitter at various times. If not for that, none of this would have happened.

If you know a few people local to you who is also independent and at roughly the same point in their journey, great! Start talking to them and suggest you formalize things somewhat and form a mastermind group.

Don’t know anyone local? No problem. Hop on Twitter, make some connections, and ask a few folks once you’ve built a relationship with them. Don’t know anyone there? Follow me on Twitter, see who I follow, see who follows me, etc.

Once you do get a group going, if you’re not sure what to talk about (especially if you’re all fairly new), I’d suggest reading a book or two together to give you some structure. Brennan Dunn’s Double Your Freelancing Rate and Alan Weiss’ Value Based Fees are both great choices. That will give you something to talk about, and help you improve your consulting acumen and pricing fairly quickly.

Need help?

If you have any questions about starting a mastermind group, what to talk about, etc., leave a comment or shoot me an email, and I’ll help however I can.

Don’t call yourself a freelancer

December 2015 update: I wrote a book on freelancing/consulting with my mastermind group. Learn more about that here.

The other day, I tweeted some advice to freelancers, including “don’t call yourself a freelancer.” I got a few responses asking why, so here’s an explanation.

In your clients’ minds, freelancers are inherently less valuable than [professional whatever you are]. Their neighbor’s unemployed college dropout kid who has a nice camera calls himself a freelancer. Their stoner second cousin who makes a pittance on Fiverr calls herself a freelancer. Is that who you want to be associated with?

Besides being asked why, I also got replies asking if not freelancer, then what? If you’re a writer, call yourself a writer. If you’re a designer, then say so. If there’s not a succinct term that encompasses what you do, then use “consultant” as your title.

Call yourself a professional, conduct yourself like a professional, deliver professional results, and set professional prices. You’ll enjoy your work a lot more, and earn more in the process.

Web Agency Podcast

I recently appeared on the Web Agency Podcast with Mat Newton, and had a great time. Mat and I talked about our (mine and my partner Michael’s) journey building our agency. We cover having a partnership, raising our prices, changing your mindset, conversion optimization, and much more. Give it a listen, and if you have any questions, I’d be happy to help.