Skip to content

Commit

Permalink
🎉 initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
w01fgang committed Apr 3, 2022
0 parents commit 983d6ba
Show file tree
Hide file tree
Showing 34 changed files with 7,277 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
lib
11 changes: 11 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
};
15 changes: 15 additions & 0 deletions .github/workflows/jest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install modules
run: yarn
- name: Run tests
run: yarn jest --json --outputFile=result.json --testLocationInResults
- uses: tanmen/jest-reporter@v1
if: always()
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
22 changes: 22 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages

name: Publish package

on:
release:
types: [created]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
registry-url: https://registry.npmjs.org
- run: npm i
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
lib
coverage
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__test__
87 changes: 87 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# React permission gate
inspired by https://isamatov.com/react-permissions-and-roles/

Easily render or hide pieces of UI relative to the user's access role.

### Supports Typescript and Flow type


### Example
full example [here](example)
```javascript
import { FeatureGateProvider } from 'feature-gate';

// define or get from api features and freeze them
const features = Object.freeze({
feature1: 'true',
ABtest: 'A',
});

function MyApp() {
const featureFlags = {}; // get from user api

return (
<FeatureGateProvider featureFlags={featureFlags} features={features}>
<App />
</FeatureGateProvider>
)
}
```
then anywhere in the app use names of features defined in the features map

```javascript
import { FeatureGate } from 'feature-gate';

<FeatureGate name="feature1">
<div>Component available for authorized user</div>
</FeatureGate>

```
```javascript
import { FeatureSwitch } from 'feature-gate';

<FeatureSwitch name="ABtest" fallback={<div>B test</div>}>
<div>A test</div>
</FeatureSwitch>

```
or use hook
```javascript
import { useFeature } from 'feature-gate';
...

const { enabled: showFeature1 } = useFeature('feature1');
// feature status for the current user
...
{showFeature1 && <div>Component available for authorized user</div>}
```
### Advanced usage
A validator function can be provided
```javascript
import { FeatureGateProvider } from 'feature-gate';
...

// define or get from api rules and freeze them
const features = Object.freeze({
feature1: 'true',
ABtest: 'A',
});

function validator({ featureFlags, features, name }) {
// default validator implementation
const feature = featureFlags[name];
if (!feature) return false;

return features[name] === feature;
}

function MyApp() {
const featureFlags = {}; // get from user api

return (
<FeatureGateProvider featureFlags={featureFlags} features={features} validator={validator}>
<App />
</FeatureGateProvider>
)
}
```
3 changes: 3 additions & 0 deletions example/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next"
}
34 changes: 34 additions & 0 deletions example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
15 changes: 15 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# TypeScript Next.js example

This is an example of using react-feature-gate

## Preview

Preview the example live on [StackBlitz](http://stackblitz.com/):

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/w01fgang/react-feature-gate/tree/main/example)

## Deploy your own

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/w01fgang/react-feature-gate/tree/main/example&project-name=react-feature-gate&repository-name=react-feature-gate)
50 changes: 50 additions & 0 deletions example/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { ReactElement, ReactNode } from 'react'
import { PermissionGate } from 'feature-gate';
import Link from 'next/link'
import Head from 'next/head'

type Props = {
children: ReactNode
title?: string
}

const Layout = ({ children, title = 'This is the default title' }: Props): ReactElement => (
<div>
<Head>
<title>{title}</title>
<meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<header>
<nav>
<Link href="/">
<a>Home</a>
</Link>
{' | '}
<Link href="/about">
<a>About</a>
</Link>

<PermissionGate name="users">
<>
{' | '}
<Link href="/users">
<a>Users List</a>
</Link>
</>
</PermissionGate>

<PermissionGate name="users-api">
<span>{' | '}<a href="/api/users">Users API</a></span>
</PermissionGate>
</nav>
</header>
{children}
<footer>
<hr />
<span>I&apos;m here to stay (Footer)</span>
</footer>
</div>
)

export default Layout
19 changes: 19 additions & 0 deletions example/components/List.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ReactElement } from 'react'
import ListItem from './ListItem'
import { User } from '../interfaces'

type Props = {
items: User[]
}

const List = ({ items }: Props): ReactElement => (
<ul>
{items.map((item) => (
<li key={item.id}>
<ListItem data={item} />
</li>
))}
</ul>
)

export default List
16 changes: 16 additions & 0 deletions example/components/ListDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ReactElement } from 'react'

import { User } from '../interfaces'

type ListDetailProps = {
item: User
}

const ListDetail = ({ item: user }: ListDetailProps): ReactElement => (
<div>
<h1>Detail for {user.name}</h1>
<p>ID: {user.id}</p>
</div>
)

export default ListDetail
18 changes: 18 additions & 0 deletions example/components/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ReactElement } from 'react'
import Link from 'next/link'

import { User } from '../interfaces'

type Props = {
data: User
}

const ListItem = ({ data }: Props): ReactElement => (
<Link href="/users/[id]" as={`/users/${data.id}`}>
<a>
{data.id}: {data.name}
</a>
</Link>
)

export default ListItem
31 changes: 31 additions & 0 deletions example/components/RoleSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ChangeEvent, ReactElement } from 'react';
import { usePermission } from 'feature-gate';

type Props = {
onChange: (role: string) => void,
};

const RoleSelector = (props: Props): ReactElement => {
const { role } = usePermission("users");

const handleChange = (e: ChangeEvent<HTMLSelectElement>) => {
props.onChange(e.target.value)
};

return (
<div id="role-selector">
<span>Select role: </span>
<select name="role" value={role} onChange={handleChange}>
<option value="user">user</option>
<option value="admin">admin</option>
</select>
<style jsx>{`
#role-selector {
margin: 8px 0;
}
`}</style>
</div>
)
}

export default RoleSelector
10 changes: 10 additions & 0 deletions example/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// You can include shared interfaces/types in a separate file
// and then use them in any component by importing them. For
// example, to import the interface below do:
//
// import { User } from 'path/to/interfaces';

export type User = {
id: number
name: string
}
3 changes: 3 additions & 0 deletions example/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
24 changes: 24 additions & 0 deletions example/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "with-typescript",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"type-check": "tsc"
},
"dependencies": {
"next": "latest",
"feature-gate": "latest",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/node": "^17.0.23",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14",
"eslint-config-next": "^12.1.4",
"typescript": "4.6"
},
"license": "MIT"
}
Loading

0 comments on commit 983d6ba

Please sign in to comment.